From 0dbf9c907dd2aef08579b0040e8e16ef01a15267 Mon Sep 17 00:00:00 2001 From: scarqin Date: Tue, 31 Dec 2024 12:01:42 +0800 Subject: [PATCH] feat: add llm status manage --- .../core/src/pages/aiSetting/AIFlowChart.tsx | 16 ++--- .../src/pages/aiSetting/AiSettingModal.tsx | 60 ++++++++++++++----- .../aiSetting/components/ModelCardNode.tsx | 40 +++++++------ .../core/src/pages/aiSetting/types.ts | 19 ++++-- 4 files changed, 88 insertions(+), 47 deletions(-) diff --git a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx index 0a68684a..a2799f5d 100644 --- a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx @@ -22,17 +22,17 @@ import { ModelCardNode } from './components/ModelCardNode' import { ServiceCardNode } from './components/NodeComponents' import { LAYOUT } from './constants' import './styles.css' -import { ModelData } from './types' +import { ModelListData } from './types' export type ApiResponse = BasicResponse<{ backup: { id: string name: string } - providers: ModelData[] + providers: ModelListData[] }> -const calculateNodePositions = (models: ModelData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => { +const calculateNodePositions = (models: ModelListData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => { return models.reduce( (acc, model, index) => { const y = startY + index * gap @@ -63,7 +63,7 @@ const edgeTypes: EdgeTypes = { } const AIFlowChart = () => { - const [modelData, setModelData] = useState([]) + const [modelData, setModelData] = useState([]) const [nodes, setNodes, onNodesChange] = useNodesState([]) const [edges, setEdges, onEdgesChange] = useEdgesState([]) const { fetchData } = useFetch() @@ -71,7 +71,8 @@ const AIFlowChart = () => { useEffect(() => { fetchData('ai/providers/configured', { - method: 'GET' + method: 'GET', + eoTransformKeys: ['default_llm'] }).then((response) => { const mockApiResponse: ApiResponse = response as ApiResponse setModelData(mockApiResponse.data.providers) @@ -84,7 +85,6 @@ const AIFlowChart = () => { const positions = calculateNodePositions(modelData) // 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', @@ -100,9 +100,9 @@ const AIFlowChart = () => { type: 'modelCard', position: positions[model.id], data: { - title: model.name, + name: model.name, status: model.status, - defaultLlm: model.default_llm, + defaultLlm: model.defaultLlm, logo: model.logo, id: model.id } diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx index f5d3e206..71ad949a 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingModal.tsx @@ -3,9 +3,9 @@ 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, Tag, Tooltip } from 'antd' +import { App, Form, InputNumber, Select, Switch, Tag, Tooltip } from 'antd' import { forwardRef, useEffect, useImperativeHandle, useState } from 'react' -import { AiProviderConfig, AiProviderLlmsItems } from './AiSettingList' +import { AiProviderConfig, AiProviderLlmsItems, ModelDetailData } from './types' export type AiSettingModalContentProps = { entity: AiProviderConfig & { defaultLlm: string } @@ -16,12 +16,6 @@ export type AiSettingModalContentHandle = { save: () => Promise } -type AiSettingModalContentField = { - config: string - defaultLlm: string - priority: number -} - const AiSettingModalContent = forwardRef((props, ref) => { const [form] = Form.useForm() const { message } = App.useApp() @@ -55,13 +49,15 @@ const AiSettingModalContent = forwardRef { + if (!isChecked) { + return '保存后供应商状态变为【停用】,使用本供应商的 API 将临时使用负载优先级最高的正常供应商。' + } + return '保存后供应商状态变为【正常】,恢复调用本供应商的 AI 能力。' + } + useImperativeHandle(ref, () => ({ save })) @@ -112,8 +115,9 @@ const AiSettingModalContent = forwardRef - label={$t('默认模型')} name="defaultLlm" rules={[{ required: true }]}> + label={$t('默认模型')} name="defaultLlm" rules={[{ required: true }]}> - + label={ {$t('负载优先级')} @@ -147,7 +151,7 @@ const AiSettingModalContent = forwardRef { if (value <= 0) { - throw new Error($t('优先级必须大于0')) + throw new Error($t('优先级必须大于 0')) } return Promise.resolve() } @@ -158,16 +162,42 @@ const AiSettingModalContent = forwardRef - label={$t('参数')} name="config"> + label={$t('API Key(默认 Key)')} name="config"> + + {entity.id && ( + +
+
+ 当前调用状态: + {entity.status === 'enabled' && {$t('正常')}} + {entity.status === 'disabled' && {$t('停用')}} + {entity.status === 'abnormal' && {$t('异常')}} +
+ + { + form.setFieldsValue({ enable: checked }) + }} + /> + +
+ {(entity.status === 'enabled' && !form.getFieldValue('enable')) || + (entity.status !== 'enabled' && form.getFieldValue('enable')) ? ( +
* {getTooltipText(form.getFieldValue('enable'))}
+ ) : null} +
+ )} ) }) diff --git a/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx index ca684709..1b381485 100644 --- a/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx +++ b/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx @@ -4,27 +4,34 @@ import { Handle, Position } from '@xyflow/react' import { t } from 'i18next' import React from 'react' import { useAiSetting } from '../contexts/AiSettingContext' -import { AiSettingListItem, ModelStatus } from '../types' +import { AiSettingListItem, ModelDetailData, ModelStatus } from '../types' -interface ModelCardData { - title: string - status: ModelStatus - logo: string - defaultLlm: string -} - -type ModelCardNodeData = ModelCardData & { +type ModelCardNodeData = ModelDetailData & { id: string position: { x: number; y: number } } export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) => { - const { title, status, defaultLlm, logo } = data + const { name, status, defaultLlm, logo } = data const { openConfigModal } = useAiSetting() const { aiConfigFlushed, setAiConfigFlushed } = useGlobalContext() + + 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 (
@@ -32,17 +39,14 @@ export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) =
-
+
- {title} - + {name} +
{/* Action buttons */} diff --git a/frontend/packages/core/src/pages/aiSetting/types.ts b/frontend/packages/core/src/pages/aiSetting/types.ts index eebcc66a..814346c9 100644 --- a/frontend/packages/core/src/pages/aiSetting/types.ts +++ b/frontend/packages/core/src/pages/aiSetting/types.ts @@ -1,4 +1,4 @@ -export type ModelStatus = 'enable' | 'abnormal'|'disabled' +export type ModelStatus = 'enabled' | 'abnormal'|'disabled' export type KeyStatus ='normal' | 'abnormal'|'disabled' export interface KeyData { @@ -7,18 +7,23 @@ export interface KeyData { status: KeyStatus, } -export interface ModelData { +export interface ModelListData { id: string name: string logo: string - default_llm: string + defaultLlm: string status: ModelStatus api_count: number key_count: number keys: KeyData[] +} +export interface ModelDetailData extends ModelListData{ + enable:boolean + config: string, priority?: number } + export type AiSettingListItem = { name: string id: string @@ -46,10 +51,12 @@ export type AiProviderDefaultConfig = { scopes: string[] } -export type AiProviderConfig = { +export interface AiProviderConfig { id: string name: string - config?: string + config: string getApikeyUrl: string - priority?: number + priority: number + enable: boolean + status: ModelStatus }