diff --git a/.github/workflows/release.yaml b/.github/workflows/release.yaml index 49b80caa..aa645b89 100644 --- a/.github/workflows/release.yaml +++ b/.github/workflows/release.yaml @@ -25,7 +25,7 @@ jobs: echo "Build frontend..." cd ./frontend && pnpm run build - name: upload frontend release - uses: actions/upload-artifact@v3 + uses: actions/upload-artifact@v4 with: name: frontend-package path: frontend/dist @@ -41,7 +41,7 @@ jobs: - name: Checkout #Checkout代码 uses: actions/checkout@v3 - name: download frontend release - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: frontend-package path: frontend/dist @@ -71,7 +71,7 @@ jobs: - uses: actions/checkout@v3 - name: download frontend release - uses: actions/download-artifact@v3 + uses: actions/download-artifact@v4 with: name: frontend-package path: frontend/dist diff --git a/README.md b/README.md index 6c52c63a..4ab6e887 100644 --- a/README.md +++ b/README.md @@ -210,7 +210,7 @@ APIPark uses the Apache 2.0 License. For more details, please refer to the LICEN For enterprise-level features and professional technical support, contact our pre-sales experts for personalized demos, customized solutions, and pricing. - Website: https://apipark.com -- Email: dev@apipark.com +- Email: contact@apipark.com
diff --git a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx deleted file mode 100644 index dfd90d5c..00000000 --- a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx +++ /dev/null @@ -1,269 +0,0 @@ -'use client' - -import { BasicResponse } from '@common/const/const' -import { useGlobalContext } from '@common/contexts/GlobalStateContext' -import { useFetch } from '@common/hooks/http' -import { $t } from '@common/locales' -import { - CoordinateExtent, - Edge, - EdgeTypes, - Node, - NodeTypes, - PanOnScrollMode, - ReactFlow, - useEdgesState, - useNodesState -} from '@xyflow/react' -import '@xyflow/react/dist/style.css' -import { Button, Space, Spin } from 'antd' -import { useCallback, useEffect, useState } from 'react' -import { useNavigate } from 'react-router-dom' -import CustomEdge from './components/CustomEdge' -import { KeyStatusNode } from './components/KeyStatusNode' -import { ModelCardNode } from './components/ModelCardNode' -import { ServiceCardNode } from './components/NodeComponents' -import { LAYOUT } from './constants' -import './styles.css' -import { ModelListData } from './types' - -export type ApiResponse = BasicResponse<{ - backup: { - id: string - name: string - } - providers: ModelListData[] -}> - -const calculateNodePositions = (models: ModelListData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => { - return models.reduce( - (acc, model, index) => { - const y = startY + index * gap - return { - ...acc, - [model.id]: { - x: LAYOUT.MODEL_NODE_X, - y - }, - [`${model.id}-keys`]: { - x: LAYOUT.KEY_NODE_X, - y: y + 16 - } - } - }, - {} as Record - ) -} - -const nodeTypes: NodeTypes = { - modelCard: ModelCardNode, - keyCard: KeyStatusNode, - serviceCard: ServiceCardNode -} as const - -const edgeTypes: EdgeTypes = { - custom: CustomEdge -} - -const AIFlowChart = () => { - const [modelData, setModelData] = useState([]) - const [loading, setLoading] = useState(false) - const [nodes, setNodes, onNodesChange] = useNodesState([]) - const [edges, setEdges, onEdgesChange] = useEdgesState([]) - const { fetchData } = useFetch() - const { aiConfigFlushed } = useGlobalContext() - const navigate = useNavigate() - - useEffect(() => { - setLoading(true) - fetchData('ai/providers/configured', { - method: 'GET', - eoTransformKeys: ['default_llm'] - // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' - }) - .then((response) => { - const mockApiResponse: ApiResponse = response as ApiResponse - setModelData(mockApiResponse.data.providers) - }) - .finally(() => { - setLoading(false) - }) - }, [aiConfigFlushed]) - - useEffect(() => { - if (!modelData.length) return - - const positions = calculateNodePositions(modelData) - const firstSuccessModel = modelData.find((model) => model.status === 'enabled') - console.log(firstSuccessModel) - - // subtract 5 to make sure the service node is aligned with the top model node - const serviceY = positions[modelData[0].id].y - 5 - const newNodes = [ - { - id: 'apiService', - type: 'serviceCard', - position: { x: LAYOUT.SERVICE_NODE_X, y: serviceY }, - draggable: false, - data: { - title: 'API Services', - count: modelData.length - } - }, - ...modelData.map((model) => ({ - id: model.id, - type: 'modelCard', - position: positions[model.id], - data: { - name: model.name, - status: model.status, - defaultLlm: model.defaultLlm, - logo: model.logo, - id: model.id, - alternativeModel: firstSuccessModel - } - })), - ...modelData.map((model) => ({ - id: `${model.id}-keys`, - type: 'keyCard', - position: positions[`${model.id}-keys`], - data: { - title: '', - keys: (model.keys || []).map((key, index) => ({ - id: key.id, - status: key.status, - priority: index + 1 - })) - } - })) - ] - - const newEdges: any = [ - ...modelData.map((model) => ({ - id: `service-${model.id}`, - source: 'apiService', - target: model.id, - label: `${model.api_count} apis`, - data: { - id: model.id, - status: model.status - }, - animated: true, - style: { stroke: model.status === 'enabled' ? '#52c41a' : '#ff4d4f' } - })), - ...modelData.map((model) => ({ - id: `${model.id}-keys-edge`, - source: model.id, - target: `${model.id}-keys`, - label: `${model.key_count} keys`, - data: { id: model.id }, - animated: true - })) - ] - setNodes(newNodes) - setEdges(newEdges) - }, [modelData]) - - const calculateExtent = useCallback(() => { - const left = LAYOUT.SERVICE_NODE_X - const right = LAYOUT.KEY_NODE_X - const top = 0 // Allow slight negative scroll to reduce top padding - const bottom = LAYOUT.NODE_START_Y + modelData.length * LAYOUT.NODE_GAP - return [ - [left, top], - [right, bottom < 100 ? 5000 : bottom] - ] as CoordinateExtent - }, [modelData.length]) - - const updateProviderOrder = async (sortedProviderIds: string[]) => { - await fetchData('ai/provider/sort', { - method: 'PUT', - body: JSON.stringify({ - providers: sortedProviderIds - }) - }) - } - - const onNodeDragStop: any = useCallback((_: any, node: Node) => { - if (node.type !== 'modelCard') return - - setNodes((nds) => { - const modelNodes = nds.filter((n) => n.type === 'modelCard') - const sortedNodes = [...modelNodes].sort((a, b) => a.position.y - b.position.y) - const sortedProviderIds = sortedNodes.map((node) => node.id) - - // Update provider order outside of setNodes callback - updateProviderOrder(sortedProviderIds) - // Update all node positions in a single pass - return nds.map((n) => { - if (n.type === 'modelCard') { - const index = sortedNodes.findIndex((sn) => sn.id === n.id) - return { - ...n, - position: { - x: LAYOUT.MODEL_NODE_X, - y: LAYOUT.NODE_START_Y + index * LAYOUT.NODE_GAP - } - } - } - if (n.type === 'keyCard') { - const modelId = n.id.replace('-keys', '') - const modelNode = sortedNodes.find((mn) => mn.id === modelId) - if (modelNode) { - const index = sortedNodes.findIndex((sn) => sn.id === modelId) - return { - ...n, - position: { - x: LAYOUT.KEY_NODE_X, - y: LAYOUT.NODE_START_Y + index * LAYOUT.NODE_GAP + 16 - } - } - } - } - return n - }) - }) - }, []) - - return ( -
- {loading ? ( -
- -
- ) : modelData.length === 0 ? ( - -
{$t('未配置 AI 模型')}
- -
- ) : ( - - )} -
- ) -} - -export default AIFlowChart diff --git a/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx b/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx deleted file mode 100644 index ba1ae334..00000000 --- a/frontend/packages/core/src/pages/aiSetting/AIUnconfigure.tsx +++ /dev/null @@ -1,136 +0,0 @@ -import Icon, { LoadingOutlined } from '@ant-design/icons' -import WithPermission from '@common/components/aoplatform/WithPermission' -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 { App, Button, Card, Empty, Spin, Tag } from 'antd' -import { memo, useEffect, useState } from 'react' -import { useNavigate } from 'react-router-dom' -import { useAiSetting } from './contexts/AiSettingContext' -import { AiSettingListItem } from './types' - -const CardBox = memo(({ provider }: { provider: AiSettingListItem }) => { - const { openConfigModal } = useAiSetting() - const navigate = useNavigate() - - const handleOpenModal = async (provider: AiSettingListItem) => { - await openConfigModal(provider) - navigate('/aisetting?status=configure') - } - - return ( - -
- - {provider.name} -
- - {provider.configured ? $t('已配置') : $t('未配置')} - - - } - className="shadow-[0_5px_10px_0_rgba(0,0,0,0.05)] rounded-[10px] overflow-visible h-[156px] m-0 flex flex-col " - classNames={{ header: 'border-b-[0px] p-[20px] px-[24px]', body: 'pt-0 flex-1' }} - > -
-
- {provider.configured && ( - <> - - {provider.defaultLlm} - - )} -
- - - -
-
- ) -}) -const ModelCardArea = ({ modelList, className }: { modelList: AiSettingListItem[]; className?: string }) => { - return ( - <> - {modelList.length > 0 ? ( -
- {modelList.map((provider: AiSettingListItem) => ( - - ))} -
- ) : ( - - )} - - ) -} - -const AIUnConfigure = () => { - const [modelData, setModelData] = useState([]) - const { fetchData } = useFetch() - const [loading, setLoading] = useState(false) - const { aiConfigFlushed } = useGlobalContext() - - useEffect(() => { - setLoading(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) { - setModelData(data.providers) - } else { - const { message } = App.useApp() - message.error(msg || $t(RESPONSE_TIPS.error)) - } - }) - .finally(() => setLoading(false)) - }, [aiConfigFlushed]) - - return ( - } - spinning={loading} - > - {modelData && modelData.length > 0 ? ( -
- {modelData.filter((item) => !item.configured).length > 0 && ( - <> - !item.configured) || []} /> - - )} -
- ) : ( - - )} -
- ) -} -export default AIUnConfigure diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx index aa7034d2..3a7151ab 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx @@ -3,9 +3,8 @@ import { useI18n } from '@common/locales' import { Tabs } from 'antd' import { useEffect, useState } from 'react' import { useSearchParams } from 'react-router-dom' -import AIFlowChart from './AIFlowChart' -import AIUnConfigure from './AIUnconfigure' import { AiSettingProvider } from './contexts/AiSettingContext' +import OnlineModelList from './OnlineModelList' const CONTENT_STYLE = { height: 'calc(-300px + 100vh)' } as const @@ -38,21 +37,17 @@ const AiSettingContent = () => { items={[ { key: 'flow', - label: $t('已设置'), + label: $t('在线模型'), children: (
- +
) }, { key: 'config', - label: $t('未设置'), - children: ( -
- -
- ) + label: $t('本地模型'), + children:
} ]} /> diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx index 83e197e1..0ba661cb 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx @@ -1,20 +1,20 @@ -import { QuestionCircleOutlined } from '@ant-design/icons' 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, InputNumber, Select, Switch, Tag, Tooltip } from 'antd' import { forwardRef, useEffect, useImperativeHandle, useRef, useState } from 'react' -import { AiProviderLlmsItems, ModelDetailData, AiSettingListItem } from './types' +import { AiProviderLlmsItems, ModelDetailData, AiSettingListItem, AISettingEntityItem } from './types' import { MemberItem, SimpleTeamItem } from '@common/const/type' import { useGlobalContext } from '@common/contexts/GlobalStateContext' export type AiSettingModalContentProps = { - entity: ModelDetailData & { defaultLlm: string } + entity?: AISettingEntityItem readOnly: boolean modelMode?: 'auto' | 'manual' + originEntity?: string /** 如果是手动选择 AI 模型,那么需要更新 footer 底部的内容,所以需要这个方法去更新外部的 footer */ - updateEntityData: (entity: ModelDetailData & { defaultLlm: string }) => void + updateEntityData: (entity: AISettingEntityItem) => void } export type AiSettingModalContentHandle = { @@ -25,7 +25,7 @@ export type AiSettingModalContentHandle = { const AiSettingModalContent = forwardRef((props, ref) => { const [form] = Form.useForm() const { message } = App.useApp() - const { entity, readOnly, modelMode = 'auto', updateEntityData } = props + const { entity, readOnly, modelMode = 'auto', updateEntityData, originEntity } = props const { fetchData } = useFetch() const [llmList, setLlmList] = useState() const [loading, setLoading] = useState(false) @@ -47,7 +47,7 @@ const AiSettingModalContent = forwardRef>(`ai/provider/llms`, { method: 'GET', - eoParams: { provider: id || localEntity.id } + eoParams: { provider: id || localEntity?.id } }) .then((response) => { const { code, data, msg } = response @@ -123,18 +123,17 @@ const AiSettingModalContent = forwardRef>(`ai/provider/config`, { method: 'GET', eoParams: { provider: id }, - eoTransformKeys: ['get_apikey_url'] + eoTransformKeys: ['get_apikey_url', 'default_llm'] }) .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 + ...data.provider } setLocalEntity(modelEntity) setFormFieldsValue(modelEntity) - updateEntityData(modelEntity) + updateEntityData?.(modelEntity) } else { message.error(msg || $t(RESPONSE_TIPS.error)) } @@ -153,26 +152,24 @@ const AiSettingModalContent = forwardRef { // 如果是直接在 AI 模型配置,则获取默认模型列表和团队列表 - if (modelMode === 'auto') { - getLlmList() + if (localEntity?.id) { + getModelConfig(localEntity.id) setFormFieldsValue(localEntity) } else { getModelProviderList() - getTeamOptionList() + originEntity && getTeamOptionList() } }, []) @@ -217,13 +214,13 @@ const AiSettingModalContent = forwardRef Promise = () => { return new Promise((resolve, reject) => { - form - .validateFields() - .then((value) => { - const finalValue = { - ...value, - priority: Math.max(1, value.priority) - } + try { + form + .validateFields() + .then((value) => { + const finalValue = { + ...value + } fetchData>('ai/provider/config', { method: 'PUT', @@ -241,10 +238,12 @@ const AiSettingModalContent = forwardRef reject(errorInfo)) - }) - .catch((errorInfo) => reject(errorInfo)) + }).catch((errorInfo) => reject(errorInfo)) + }) + .catch((errorInfo) => reject(errorInfo)) + } catch (error) { + reject(error) + } }) } @@ -307,43 +306,13 @@ const AiSettingModalContent = forwardRef - {modelMode === 'auto' && ( - - label={ - - {$t('负载优先级')} - - - - - } - name="priority" - rules={[ - { required: true }, - { - validator: async (_, value) => { - if (value <= 0) { - throw new Error($t('优先级必须大于 0')) - } - return Promise.resolve() - } - } - ]} - initialValue={1} - > - - - )} - {modelMode === 'manual' && ( + {originEntity === 'guide' && ( )} - label={$t('API Key(默认 Key)')} name="config"> - - {localEntity?.configured && ( + {originEntity !== 'guide' && (
{$t('当前调用状态:')} - {localEntity.status === 'enabled' && {$t('正常')}} - {localEntity.status === 'disabled' && {$t('停用')}} - {localEntity.status === 'abnormal' && {$t('异常')}} + {localEntity?.status === 'enabled' && {$t('正常')}} + {localEntity?.status === 'disabled' && {$t('停用')}} + {localEntity?.status === 'abnormal' && {$t('异常')}}
- {(localEntity.status === 'enabled' && !enableState) || (localEntity.status !== 'enabled' && enableState) ? ( + {(localEntity?.status === 'enabled' && !enableState) || (localEntity?.status !== 'enabled' && enableState) ? (
* {getTooltipText(enableState)}
) : null}
diff --git a/frontend/packages/core/src/pages/aiSetting/OnlineModelList.tsx b/frontend/packages/core/src/pages/aiSetting/OnlineModelList.tsx new file mode 100644 index 00000000..b59e6541 --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/OnlineModelList.tsx @@ -0,0 +1,167 @@ +import { ActionType } from '@ant-design/pro-components' +import PageList, { PageProColumns } from '@common/components/aoplatform/PageList' +import TableBtnWithPermission from '@common/components/aoplatform/TableBtnWithPermission' +import { BasicResponse, RESPONSE_TIPS, STATUS_CODE } from '@common/const/const' +import { useFetch } from '@common/hooks/http' +import { $t } from '@common/locales' +import { App, Divider, Space, Typography } from 'antd' +import React, { useRef, useState } from 'react' +import { useAiSetting } from './contexts/AiSettingContext' +import { AiSettingListItem, ModelListData } from './types' + +const OnlineModelList: React.FC = () => { + const pageListRef = useRef(null) + const { message } = App.useApp() + const { fetchData } = useFetch() + const [searchWord, setSearchWord] = useState('') + const [total, setTotal] = useState(0) + const { openConfigModal } = useAiSetting() + + const handleEdit = (record: ModelListData) => { + openConfigModal({ id: record.id, defaultLlm: record.defaultLlm } as AiSettingListItem) + } + + const handleAdd = () => { + openConfigModal() + } + + const handleDelete = async (id: string) => { + try { + const response = await fetchData>('ai/provider', { + method: 'DELETE', + eoParams: { + provider: id + } + // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' + }) + + if (response.code === STATUS_CODE.SUCCESS) { + message.success($t('删除成功')) + pageListRef.current?.reload() + } else { + message.error(response.msg || RESPONSE_TIPS.error) + } + } catch (error) { + message.error(RESPONSE_TIPS.error) + } + } + + const requestList = async (params: any) => { + try { + const response = await fetchData>('ai/providers/configured', { + method: 'GET', + eoParams: { + page_size: params.pageSize, + keyword: searchWord, + page: params.current + }, + eoTransformKeys: ['default_llm'] + // eoApiPrefix: 'http://uat.apikit.com:11204/mockApi/aoplatform/api/v1/' + }) + + if (response.code === STATUS_CODE.SUCCESS) { + setTotal(response.data.total) + return { + data: response.data.providers, + success: true, + total: response.data.total + } + } else { + message.error(response.msg || $t(RESPONSE_TIPS.error)) + return { + data: [], + success: false, + total: response.data.total + } + } + } catch (error) { + return { + data: [], + success: false, + total: 0 + } + } + } + const statusEnum = { + enabled: { text: {$t('正常')} }, + disabled: { text: {$t('停用')} }, + abnormal: { text: {$t('异常')} } + } + + const operation: PageProColumns[] = [ + { + title: '', + key: 'option', + btnNums: 4, + fixed: 'right', + valueType: 'option', + render: (_: React.ReactNode, entity: ModelListData) => [ + handleEdit(entity)} + btnTitle={$t('设置')} + />, + , + handleDelete(entity.id as string)} + btnTitle={$t('删除')} + /> + ] + } + ] + + const columns: PageProColumns[] = [ + { + title: $t('名称'), + dataIndex: 'name', + render: (dom: React.ReactNode, entity: ModelListData) => {entity.name} + }, + { + title: $t('状态'), + dataIndex: 'status', + ellipsis: true, + valueType: 'select', + // filters: true, + // onFilter: true, + valueEnum: statusEnum, + render: (dom: React.ReactNode, entity: ModelListData) => statusEnum[entity.status]?.text || entity.status + }, + { + title: $t('默认模型'), + dataIndex: 'defaultLlm' + }, + { + title: $t('Apis'), + dataIndex: 'api_count' + }, + { + title: $t('Keys'), + dataIndex: 'key_count' + }, + ...operation + ] + + return ( + { + setSearchWord(e.target.value) + pageListRef.current?.reload() + }} + showPagination={true} + searchPlaceholder={$t('请输入名称搜索')} + columns={columns} + addNewBtnTitle={$t('添加模型')} + onAddNewBtnClick={handleAdd} + /> + ) +} + +export default OnlineModelList diff --git a/frontend/packages/core/src/pages/aiSetting/components/CustomEdge.tsx b/frontend/packages/core/src/pages/aiSetting/components/CustomEdge.tsx deleted file mode 100644 index 35d585f5..00000000 --- a/frontend/packages/core/src/pages/aiSetting/components/CustomEdge.tsx +++ /dev/null @@ -1,72 +0,0 @@ -import { BaseEdge, EdgeLabelRenderer, EdgeProps, getSmoothStepPath, useStore } from '@xyflow/react' - -export default function CustomEdge({ - id, - sourceX, - sourceY, - targetX, - targetY, - sourcePosition, - targetPosition, - style = {}, - markerEnd, - label, - data, - source, - target -}: EdgeProps) { - // Get all edges to check for duplicates - const edges = useStore((state) => state.edges) - - // Find duplicate edges between the same source and target - const duplicateEdges = edges.filter((edge) => edge.source === source && edge.target === target) - const edgeIndex = duplicateEdges.findIndex((edge) => edge.id === id) - - // Adjust the path if this is a duplicate edge - const offset = edgeIndex * 20 // 20px offset for each duplicate edge - - const [edgePath] = getSmoothStepPath({ - sourceX, - sourceY: sourceY, - sourcePosition, - targetX, - targetY: targetY + offset, - targetPosition, - borderRadius: 16 - }) - - const modelId = data?.id - - return ( - <> - - {label && ( - - - {label} - - - )} - - ) -} diff --git a/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx deleted file mode 100644 index ef24aee6..00000000 --- a/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx +++ /dev/null @@ -1,53 +0,0 @@ -import { Handle, Position } from '@xyflow/react' -import React from 'react' -import { KeyData } from '../types' - -interface KeyStatusNodeData { - id: string - title: string - keys: KeyData[] -} - -const KEY_SIZE = '1.25rem' // 20px -const KEY_GAP = '0.25rem' // 4px -const MAX_KEYS = 10 - -export const KeyStatusNode: React.FC<{ data: KeyStatusNodeData }> = ({ data }) => { - const { title, keys = [] } = data - const totalKeys = keys.length - const keyWidth = totalKeys > 5 ? `calc((100% - ${(totalKeys - 1) * 0.25}rem) / ${totalKeys})` : KEY_SIZE - return ( -
- -
-
{title}
-
5 ? '118px' : 'auto', - maxWidth: `calc(${MAX_KEYS} * ${KEY_SIZE} + (${MAX_KEYS} - 1) * ${KEY_GAP})`, - minHeight: KEY_SIZE - }} - > - {keys.map((key) => ( -
- ))} -
-
-
- ) -} diff --git a/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx deleted file mode 100644 index ede8936b..00000000 --- a/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx +++ /dev/null @@ -1,76 +0,0 @@ -import { $t } from '@common/locales' -import { Icon } from '@iconify/react' -import { Handle, Position } from '@xyflow/react' -import React from 'react' -import { useAiSetting } from '../contexts/AiSettingContext' -import { AiSettingListItem, ModelDetailData, ModelStatus } from '../types' - -type ModelCardNodeData = ModelDetailData & { - id: string - position: { x: number; y: number } - alternativeModel?: ModelDetailData -} - -export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) => { - const { name, status, defaultLlm, logo, alternativeModel } = data - const { openConfigModal } = useAiSetting() - - const getStatusIcon = (status: ModelStatus) => { - switch (status) { - case 'enabled': - return { icon: 'mdi:check-circle', color: 'text-green-500' } - case 'disabled': - return { icon: 'mdi:pause-circle', color: 'text-gray-400' } - case 'abnormal': - return { icon: 'mdi:alert-circle', color: 'text-red-500' } - } - } - - const statusConfig = getStatusIcon(status) - - return ( - <> -
- - -
-
-
-
- -
- {name} - -
- - {/* Action buttons */} -
- { - openConfigModal({ id: data.id, defaultLlm: defaultLlm } as AiSettingListItem) - }} - /> -
-
-
- {$t('默认:')} - {defaultLlm} -
-
-
- {status !== 'enabled' && alternativeModel && ( -
- {$t('关联 API 已转用')} {alternativeModel.name}/{alternativeModel.defaultLlm} -
- )} - - ) -} diff --git a/frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx b/frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx deleted file mode 100644 index e2626cc2..00000000 --- a/frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx +++ /dev/null @@ -1,3 +0,0 @@ -export { KeyStatusNode } from './KeyStatusNode' -export { ModelCardNode } from './ModelCardNode' -export { ServiceCardNode } from './ServiceCardNode' diff --git a/frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx deleted file mode 100644 index 176d80da..00000000 --- a/frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx +++ /dev/null @@ -1,18 +0,0 @@ -import { Icon } from '@iconify/react' -import { Handle, NodeProps, Position } from '@xyflow/react' -import React from 'react' - -export const ServiceCardNode: React.FC = () => { - return ( -
- -
- - AI Services -
-
- ) -} diff --git a/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx b/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx index f8e078bd..1fb60bba 100644 --- a/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx +++ b/frontend/packages/core/src/pages/aiSetting/contexts/AiSettingContext.tsx @@ -1,45 +1,39 @@ import Icon from '@ant-design/icons' -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 { App } from 'antd' import { createContext, useContext, useRef } from 'react' import AiSettingModalContent, { AiSettingModalContentHandle } from '../AiSettingModal' -import { AiSettingListItem, ModelDetailData } from '../types' +import { AiSettingListItem } from '../types' interface AiSettingContextType { - openConfigModal: (entity: AiSettingListItem) => Promise + openConfigModal: (entity?: AiSettingListItem) => Promise } const AiSettingContext = createContext(undefined) export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ children }) => { - const { modal, message } = App.useApp() - const { fetchData } = useFetch() + const { modal } = App.useApp() const { aiConfigFlushed, setAiConfigFlushed, accessData } = useGlobalContext() const modalRef = useRef() + const entityData = useRef(null) - 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 + const openConfigModal = async (entity?: AiSettingListItem) => { + // 更新弹窗 + const updateEntityData = (data: any) => { + entityData.current = data + // 更新弹窗 + modalInstance.update({}) } - - modal.confirm({ + const modalInstance = modal.confirm({ title: $t('模型配置'), content: ( ), @@ -58,10 +52,10 @@ export const AiSettingProvider: React.FC<{ children: React.ReactNode }> = ({ chi - {$t('从 (0) 获取 API KEY', [data.provider.name])} + {$t('从 (0) 获取 API KEY', [entityData.current?.name])}
diff --git a/frontend/packages/core/src/pages/aiSetting/types.ts b/frontend/packages/core/src/pages/aiSetting/types.ts index 6a8a4598..4dcc6f05 100644 --- a/frontend/packages/core/src/pages/aiSetting/types.ts +++ b/frontend/packages/core/src/pages/aiSetting/types.ts @@ -1,33 +1,37 @@ -export type ModelStatus = 'enabled' | 'abnormal'|'disabled' -export type KeyStatus ='normal' | 'abnormal'|'disabled' +export type ModelStatus = 'enabled' | 'abnormal' | 'disabled' +export type KeyStatus = 'normal' | 'abnormal' | 'disabled' export interface KeyData { id: string name: string - status: KeyStatus, + status: KeyStatus } export interface ModelListData { - id: string + id: string | undefined name: string logo: string - defaultLlm: string + defaultLlm: string | undefined modelMode?: string status: ModelStatus api_count: number key_count: number keys: KeyData[] } -export interface ModelDetailData extends ModelListData{ - enable:boolean - config: string, - priority?: number + +export interface AISettingEntityItem { + id: string | undefined + status?: ModelStatus | undefined + defaultLlm: string | undefined +} +export interface ModelDetailData extends ModelListData { + enable: boolean + config: string getApikeyUrl: string status: ModelStatus configured: boolean } - export type AiSettingListItem = { name: string id: string @@ -54,5 +58,3 @@ export type AiProviderDefaultConfig = { defaultLlm: string scopes: string[] } - - diff --git a/frontend/packages/core/src/pages/guide/AIModelGuide.tsx b/frontend/packages/core/src/pages/guide/AIModelGuide.tsx index 8563a318..649f4ffa 100644 --- a/frontend/packages/core/src/pages/guide/AIModelGuide.tsx +++ b/frontend/packages/core/src/pages/guide/AIModelGuide.tsx @@ -68,6 +68,7 @@ export const AIModelGuide = () => { ref={modalRef} modelMode="manual" updateEntityData={updateEntityData} + originEntity="guide" readOnly={!checkAccess('system.devops.ai_provider.edit', accessData)} /> ), diff --git a/frontend/packages/core/src/pages/playground/index.tsx b/frontend/packages/core/src/pages/playground/index.tsx index 12f5ec43..12a1b689 100644 --- a/frontend/packages/core/src/pages/playground/index.tsx +++ b/frontend/packages/core/src/pages/playground/index.tsx @@ -1,7 +1,5 @@ 'use client' -import AIFlowChart from '../aiSetting/AIFlowChart' - export default function Playground() { - return + return