diff --git a/frontend/packages/common/src/types/iconpark.d.ts b/frontend/packages/common/src/types/iconpark.d.ts new file mode 100644 index 00000000..4797b6f3 --- /dev/null +++ b/frontend/packages/common/src/types/iconpark.d.ts @@ -0,0 +1,10 @@ +declare namespace JSX { + interface IntrinsicElements { + 'iconpark-icon': { + name?: string; + size?: string | number; + color?: string; + [key: string]: any; + } + } +} diff --git a/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx new file mode 100644 index 00000000..c490b507 --- /dev/null +++ b/frontend/packages/core/src/pages/aiSetting/AIFlowChart.tsx @@ -0,0 +1,446 @@ +'use client' + +import { addEdge, NodeTypes, ReactFlow, useEdgesState, useNodesState } from '@xyflow/react' +import '@xyflow/react/dist/style.css' +import { useCallback, useEffect, useState } from 'react' +import { KeyStatusNode } from './components/KeyStatusNode' +import { ModelCardNode } from './components/ModelCardNode' +import { ServiceCardNode } from './components/NodeComponents' +import { ModelData } from './components/types' +import { LAYOUT } from './constants' +import './styles.css' + +interface ApiResponse { + data: { + backup: { + id: string + name: string + } + providers: ModelData[] + } + code: number + success: string +} + +const calculateNodePositions = (models: ModelData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => { + return models.reduce( + (acc, model, index) => { + acc[model.id] = { + x: LAYOUT.MODEL_NODE_X, + y: startY + index * gap + } + acc[`${model.id}-keys`] = { + x: LAYOUT.KEY_NODE_X, + y: startY + index * gap + } + return acc + }, + {} as Record + ) +} + +const nodeTypes: NodeTypes = { + modelCard: ModelCardNode, + keyCard: KeyStatusNode, + serviceCard: ServiceCardNode +} as const + +const Playground = () => { + const [modelData, setModelData] = useState([]) + const [nodes, setNodes, onNodesChange] = useNodesState([]) + const [edges, setEdges, onEdgesChange] = useEdgesState([]) + + useEffect(() => { + // Mock API call - replace with actual API call + const mockApiResponse: ApiResponse = { + code: 0, + msg: 'default_value', + data: { + providers: [ + { + id: '320000199612026216', + name: 'iFLYTEK SPARK', + logo: '\n\n \n\n', + status: 'enable', + default_llm: 'fakegpt-1.0', + api_count: 15, + key_count: 4, + keys: [ + { + id: '140000200804035469', + name: 'Margaret Perez', + status: 'normal' + }, + { + id: '460000198105142251', + name: 'Paul Moore', + status: 'disable' + }, + { + id: '640000198609159464', + name: 'Timothy Clark', + status: 'disable' + }, + { + id: '370000199901139021', + name: 'Mark Williams', + status: 'abnormal' + } + ] + }, + { + id: '640000200408193359', + name: 'OpenRouter', + logo: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + status: 'disable', + default_llm: 'fakegpt-1.0', + api_count: 2, + key_count: 7, + keys: [ + { + id: '540000200004175839', + name: 'Maria Lewis', + status: 'abnormal' + }, + { + id: '510000199111048228', + name: 'Melissa Wilson', + status: 'disable' + } + ] + }, + { + id: '43000019760318266X', + name: '360 AI', + logo: '\n\n \n\n', + status: 'disable', + default_llm: 'fakegpt-1.0', + api_count: 3, + key_count: 5, + keys: [ + { + id: '150000201012143900', + name: 'Donald Johnson', + status: 'abnormal' + }, + { + id: '520000197104044707', + name: 'Daniel Perez', + status: 'normal' + }, + { + id: '230000200610198413', + name: 'Kimberly Lee', + status: 'abnormal' + } + ] + }, + { + id: '440000202405123330', + name: 'SiliconFlow', + logo: '\n\n\n\n\n\n\n\n\n\n\n', + status: 'abnormal', + default_llm: 'gpt-3.5-turbo-0125', + api_count: 6, + key_count: 6, + keys: [ + { + id: '500000199805052887', + name: 'Sharon Robinson', + status: 'abnormal' + }, + { + id: '110000198212026854', + name: 'Carol Thompson', + status: 'abnormal' + }, + { + id: '340000201911295145', + name: 'Mark Davis', + status: 'disable' + }, + { + id: '320000198302065610', + name: 'Donna Garcia', + status: 'disable' + } + ] + }, + { + id: '620000199406027251', + name: 'FakeGPT', + logo: '\n\n \n\n', + status: 'disable', + default_llm: 'gpt-3.5-turbo-0125', + api_count: 15, + key_count: 2, + keys: [ + { + id: '650000200812274267', + name: 'Jeffrey Taylor', + status: 'normal' + }, + { + id: '420000197301023899', + name: 'Ruth Jones', + status: 'abnormal' + }, + { + id: '820000201312084850', + name: 'Steven White', + status: 'disable' + } + ] + }, + { + id: '520000200005228695', + name: '360 AI', + logo: '\n\n \n\n', + status: 'enable', + default_llm: 'fakegpt-1.0', + api_count: 19, + key_count: 9, + keys: [ + { + id: '330000197006296104', + name: 'Karen Martinez', + status: 'abnormal' + }, + { + id: '420000199601313686', + name: 'Scott Davis', + status: 'disable' + } + ] + }, + { + id: '500000202103204115', + name: 'Google Gemini', + logo: '\n\n \n\n', + status: 'abnormal', + default_llm: 'gpt-3.5-turbo-0125', + api_count: 2, + key_count: 7, + keys: [ + { + id: '140000202101093813', + name: 'Jose Harris', + status: 'normal' + }, + { + id: '410000201802115211', + name: 'Richard Johnson', + status: 'abnormal' + } + ] + }, + { + id: '130000202402241558', + name: 'Google Gemini', + logo: '\n\n \n\n', + status: 'enable', + default_llm: 'gpt-3.5-turbo-0125', + api_count: 2, + key_count: 9, + keys: [ + { + id: '370000198910185957', + name: 'Christopher Thomas', + status: 'abnormal' + }, + { + id: '450000201505255688', + name: 'Mark Williams', + status: 'normal' + }, + { + id: '150000198812074566', + name: 'David Young', + status: 'disable' + } + ] + }, + { + id: '120000200209263834', + name: 'AWS Bedrock', + logo: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', + status: 'enable', + default_llm: 'fakegpt-1.0', + api_count: 6, + key_count: 1, + keys: [ + { + id: '310000200804263420', + name: 'Daniel Lewis', + status: 'normal' + }, + { + id: '370000201306032780', + name: 'Richard Walker', + status: 'abnormal' + }, + { + id: '500000200211133087', + name: 'Frank Jones', + status: 'disable' + }, + { + id: '350000199105019471', + name: 'Carol Wilson', + status: 'abnormal' + } + ] + }, + { + id: '110000198411203825', + name: 'deepseek', + logo: '\n\n\n\n\n\n\n\n\n\n\n', + status: 'abnormal', + default_llm: 'fakegpt-1.0', + api_count: 1, + key_count: 10, + keys: [ + { + id: '210000200509114330', + name: 'Jason Martin', + status: 'abnormal' + } + ] + } + ], + backup: '' + }, + msg_zh: 'default_value' + } + setModelData(mockApiResponse.data.providers) + }, []) + + useEffect(() => { + if (!modelData.length) return + + const newNodes = [ + { + id: 'service', + type: 'serviceCard', + position: { x: LAYOUT.SERVICE_NODE_X, y: LAYOUT.NODE_START_Y }, + data: {} + }, + ...modelData.map((model) => ({ + id: model.id, + type: 'modelCard', + position: calculateNodePositions(modelData)[model.id], + data: { + title: model.name, + status: model.status, + defaultModel: model.default_llm, + logo: model.logo + } + })), + ...modelData.map((model) => ({ + id: `${model.id}-keys`, + type: 'keyCard', + position: calculateNodePositions(modelData)[`${model.id}-keys`], + data: { + title: 'API Keys', + keys: model.keys.map((key, index) => ({ + id: key.id, + status: key.status === 'normal' ? 'enabled' : 'disable', + priority: index + 1 + })) + } + })) + ] + + const newEdges = [ + ...modelData.map((model) => ({ + id: `service-${model.id}`, + source: 'service', + target: model.id, + label: `apis(${model.api_count})`, + style: { stroke: '#ddd', cursor: 'pointer' }, + type: 'smoothstep', + markerEnd: { type: 'arrow' } + })), + ...modelData.map((model) => ({ + id: `${model.id}-keys`, + source: model.id, + type: 'smoothstep', + target: `${model.id}-keys`, + animated: true, + style: { stroke: '#ddd' } + })) + ] + + setNodes(newNodes) + setEdges(newEdges) + }, [modelData]) + + const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges]) + + const onNodeDrag = useCallback( + (_: any, node: any) => { + if (node.type !== 'modelCard') return + + setNodes((nds) => { + return nds.map((n) => { + if (n.type === 'keyCard' && n.id === `${node.id}-keys`) { + return { + ...n, + position: { + x: LAYOUT.KEY_NODE_X, + y: node.position.y + } + } + } + return n + }) + }) + }, + [setNodes] + ) + + const onNodeDragStop = useCallback( + (_: any, node: any) => { + 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) + + 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 + } + } + } + return n + }) + }) + }, + [setNodes] + ) + + return ( +
+ +
+ ) +} + +export default Playground diff --git a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx index 64613bd2..3c2ba3e4 100644 --- a/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx +++ b/frontend/packages/core/src/pages/aiSetting/AiSettingList.tsx @@ -9,6 +9,7 @@ import { checkAccess } from '@common/utils/permission' import { Icon } from '@iconify/react/dist/iconify.js' import { App, Button, Card, Divider, Empty, Spin, Tag } from 'antd' import { memo, useEffect, useRef, useState } from 'react' +import AIFlowChart from './AIFlowChart' import AiSettingModalContent, { AiSettingModalContentHandle } from './AiSettingModal' export type AiSettingListItem = { @@ -54,10 +55,11 @@ const AiSettingList = () => { const getAiSettingList = () => { setLoading(true) - return fetchData< - BasicResponse<{ providers: Omit[] }> - >(`ai/providers`, { method: 'GET', eoTransformKeys: ['default_llm', 'default_llm_logo'] }) - .then(response => { + return fetchData[] }>>( + `ai/providers`, + { method: 'GET', eoTransformKeys: ['default_llm', 'default_llm_logo'] } + ) + .then((response) => { const { code, data, msg } = response if (code === STATUS_CODE.SUCCESS) { setAiSettingList( @@ -90,10 +92,11 @@ const AiSettingList = () => { const openModal = 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'] } - ) + 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)) @@ -109,7 +112,7 @@ const AiSettingList = () => { /> ), onOk: () => { - return modalRef.current?.save().then(res => { + return modalRef.current?.save().then((res) => { if (res === true) setAiConfigFlushed(true) getAiSettingList() }) @@ -193,13 +196,7 @@ const AiSettingList = () => { ) }) - const ModelCardArea = ({ - modelList, - className - }: { - modelList: AiSettingListItem[] - className?: string - }) => { + const ModelCardArea = ({ modelList, className }: { modelList: AiSettingListItem[]; className?: string }) => { return ( <> {modelList.length > 0 ? ( @@ -230,20 +227,8 @@ const AiSettingList = () => { description={$t('配置好 AI 模型后,你可以使用对应的大模型来创建 AI 服务')} showBorder={false} scrollPage={false} - // customBtn={ - // - // - // - // } > + { > {aiSettingList && aiSettingList.length > 0 ? (
-

{$t('已配置')}

- item.configured) || []} - /> - {aiSettingList.filter(item => !item.configured).length > 0 && ( + {aiSettingList.filter((item) => !item.configured).length > 0 && ( <> -

- {$t('未配置')} -

- !item.configured) || []} /> +

{$t('未配置')}

+ !item.configured) || []} /> )}
diff --git a/frontend/packages/core/src/pages/playground/components/KeyStatusNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx similarity index 92% rename from frontend/packages/core/src/pages/playground/components/KeyStatusNode.tsx rename to frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx index 8a7bc6b6..4a6d7ddd 100644 --- a/frontend/packages/core/src/pages/playground/components/KeyStatusNode.tsx +++ b/frontend/packages/core/src/pages/aiSetting/components/KeyStatusNode.tsx @@ -3,6 +3,7 @@ import React from 'react' import { KeyData } from './types' interface KeyStatusNodeData { + id: string title: string keys: KeyData[] } @@ -40,7 +41,7 @@ export const KeyStatusNode: React.FC<{ data: KeyStatusNodeData }> = ({ data }) = }} className={` rounded-md flex-shrink-0 - ${key.status === 'normal' ? 'bg-green-500' : 'bg-red-500'} + ${key.status === 'enabled' || key.status === 'normal' ? 'bg-green-500' : 'bg-red-500'} transition-all duration-200 hover:opacity-80 `} /> diff --git a/frontend/packages/core/src/pages/playground/components/ModelCardNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx similarity index 83% rename from frontend/packages/core/src/pages/playground/components/ModelCardNode.tsx rename to frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx index 01571c8b..34871690 100644 --- a/frontend/packages/core/src/pages/playground/components/ModelCardNode.tsx +++ b/frontend/packages/core/src/pages/aiSetting/components/ModelCardNode.tsx @@ -1,6 +1,7 @@ import { Icon } from '@iconify/react' import { Handle, Position } from '@xyflow/react' import { Avatar } from 'antd' +import { t } from 'i18next' import React from 'react' import { ModelStatus } from './types' @@ -18,7 +19,6 @@ type ModelCardNodeData = ModelCardData & { export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) => { const { title, status, defaultModel, logo } = data - return (
= ({ data }) = = ({ data }) = /> ) : undefined } + // TODO use https://www.npmjs.com/package/@icon-park/react icon={logo ? '' : } > {' '} @@ -58,11 +59,14 @@ export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) = console.log('Settings', data.id)} + onClick={() => console.log('Default:', data.id)} />
-
{defaultModel}
+
+ {t('默认:')} + {defaultModel} +
) diff --git a/frontend/packages/core/src/pages/playground/components/NodeComponents.tsx b/frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx similarity index 100% rename from frontend/packages/core/src/pages/playground/components/NodeComponents.tsx rename to frontend/packages/core/src/pages/aiSetting/components/NodeComponents.tsx diff --git a/frontend/packages/core/src/pages/playground/components/ServiceCardNode.tsx b/frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx similarity index 100% rename from frontend/packages/core/src/pages/playground/components/ServiceCardNode.tsx rename to frontend/packages/core/src/pages/aiSetting/components/ServiceCardNode.tsx diff --git a/frontend/packages/core/src/pages/playground/components/types.ts b/frontend/packages/core/src/pages/aiSetting/components/types.ts similarity index 100% rename from frontend/packages/core/src/pages/playground/components/types.ts rename to frontend/packages/core/src/pages/aiSetting/components/types.ts diff --git a/frontend/packages/core/src/pages/playground/constants.ts b/frontend/packages/core/src/pages/aiSetting/constants.ts similarity index 100% rename from frontend/packages/core/src/pages/playground/constants.ts rename to frontend/packages/core/src/pages/aiSetting/constants.ts diff --git a/frontend/packages/core/src/pages/playground/styles.css b/frontend/packages/core/src/pages/aiSetting/styles.css similarity index 100% rename from frontend/packages/core/src/pages/playground/styles.css rename to frontend/packages/core/src/pages/aiSetting/styles.css diff --git a/frontend/packages/core/src/pages/playground/index.tsx b/frontend/packages/core/src/pages/playground/index.tsx index 1561ca59..12f5ec43 100644 --- a/frontend/packages/core/src/pages/playground/index.tsx +++ b/frontend/packages/core/src/pages/playground/index.tsx @@ -1,448 +1,7 @@ 'use client' -import { addEdge, NodeTypes, ReactFlow, useEdgesState, useNodesState } from '@xyflow/react' -import '@xyflow/react/dist/style.css' -import { useCallback, useEffect, useState } from 'react' -import { KeyStatusNode, ModelCardNode, ServiceCardNode } from './components/NodeComponents' -import { ModelData } from './components/types' -import { LAYOUT } from './constants' -import './styles.css' +import AIFlowChart from '../aiSetting/AIFlowChart' -interface ApiResponse { - data: { - backup: { - id: string - name: string - } - providers: ModelData[] - } - code: number - success: string +export default function Playground() { + return } - -const calculateNodePositions = (models: ModelData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => { - return models.reduce( - (acc, model, index) => { - acc[model.id] = { - x: LAYOUT.MODEL_NODE_X, - y: startY + index * gap - } - acc[`${model.id}-keys`] = { - x: LAYOUT.KEY_NODE_X, - y: startY + index * gap - } - return acc - }, - {} as Record - ) -} - -const nodeTypes: NodeTypes = { - modelCard: ModelCardNode, - keyCard: KeyStatusNode, - serviceCard: ServiceCardNode -} as const - -const Playground = () => { - const [modelData, setModelData] = useState([]) - const [nodes, setNodes, onNodesChange] = useNodesState([]) - const [edges, setEdges, onEdgesChange] = useEdgesState([]) - - useEffect(() => { - // Mock API call - replace with actual API call - const mockApiResponse: ApiResponse = { - code: 0, - msg: 'default_value', - data: { - providers: [ - { - id: '320000199612026216', - name: 'iFLYTEK SPARK', - logo: '\n\n \n\n', - status: 'enable', - default_llm: 'fakegpt-1.0', - api_count: 15, - key_count: 4, - keys: [ - { - id: '140000200804035469', - name: 'Margaret Perez', - status: 'normal' - }, - { - id: '460000198105142251', - name: 'Paul Moore', - status: 'disable' - }, - { - id: '640000198609159464', - name: 'Timothy Clark', - status: 'disable' - }, - { - id: '370000199901139021', - name: 'Mark Williams', - status: 'abnormal' - } - ] - }, - { - id: '640000200408193359', - name: 'OpenRouter', - logo: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', - status: 'disable', - default_llm: 'fakegpt-1.0', - api_count: 2, - key_count: 7, - keys: [ - { - id: '540000200004175839', - name: 'Maria Lewis', - status: 'abnormal' - }, - { - id: '510000199111048228', - name: 'Melissa Wilson', - status: 'disable' - } - ] - }, - { - id: '43000019760318266X', - name: '360 AI', - logo: '\n\n \n\n', - status: 'disable', - default_llm: 'fakegpt-1.0', - api_count: 3, - key_count: 5, - keys: [ - { - id: '150000201012143900', - name: 'Donald Johnson', - status: 'abnormal' - }, - { - id: '520000197104044707', - name: 'Daniel Perez', - status: 'normal' - }, - { - id: '230000200610198413', - name: 'Kimberly Lee', - status: 'abnormal' - } - ] - }, - { - id: '440000202405123330', - name: 'SiliconFlow', - logo: '\n\n\n\n\n\n\n\n\n\n\n', - status: 'abnormal', - default_llm: 'gpt-3.5-turbo-0125', - api_count: 6, - key_count: 6, - keys: [ - { - id: '500000199805052887', - name: 'Sharon Robinson', - status: 'abnormal' - }, - { - id: '110000198212026854', - name: 'Carol Thompson', - status: 'abnormal' - }, - { - id: '340000201911295145', - name: 'Mark Davis', - status: 'disable' - }, - { - id: '320000198302065610', - name: 'Donna Garcia', - status: 'disable' - } - ] - }, - { - id: '620000199406027251', - name: 'FakeGPT', - logo: '\n\n \n\n', - status: 'disable', - default_llm: 'gpt-3.5-turbo-0125', - api_count: 15, - key_count: 2, - keys: [ - { - id: '650000200812274267', - name: 'Jeffrey Taylor', - status: 'normal' - }, - { - id: '420000197301023899', - name: 'Ruth Jones', - status: 'abnormal' - }, - { - id: '820000201312084850', - name: 'Steven White', - status: 'disable' - } - ] - }, - { - id: '520000200005228695', - name: '360 AI', - logo: '\n\n \n\n', - status: 'enable', - default_llm: 'fakegpt-1.0', - api_count: 19, - key_count: 9, - keys: [ - { - id: '330000197006296104', - name: 'Karen Martinez', - status: 'abnormal' - }, - { - id: '420000199601313686', - name: 'Scott Davis', - status: 'disable' - } - ] - }, - { - id: '500000202103204115', - name: 'Google Gemini', - logo: '\n\n \n\n', - status: 'abnormal', - default_llm: 'gpt-3.5-turbo-0125', - api_count: 2, - key_count: 7, - keys: [ - { - id: '140000202101093813', - name: 'Jose Harris', - status: 'normal' - }, - { - id: '410000201802115211', - name: 'Richard Johnson', - status: 'abnormal' - } - ] - }, - { - id: '130000202402241558', - name: 'Google Gemini', - logo: '\n\n \n\n', - status: 'enable', - default_llm: 'gpt-3.5-turbo-0125', - api_count: 2, - key_count: 9, - keys: [ - { - id: '370000198910185957', - name: 'Christopher Thomas', - status: 'abnormal' - }, - { - id: '450000201505255688', - name: 'Mark Williams', - status: 'normal' - }, - { - id: '150000198812074566', - name: 'David Young', - status: 'disable' - } - ] - }, - { - id: '120000200209263834', - name: 'AWS Bedrock', - logo: '\n\n\n\n\n\n\n\n\n\n\n\n\n\n\n', - status: 'enable', - default_llm: 'fakegpt-1.0', - api_count: 6, - key_count: 1, - keys: [ - { - id: '310000200804263420', - name: 'Daniel Lewis', - status: 'normal' - }, - { - id: '370000201306032780', - name: 'Richard Walker', - status: 'abnormal' - }, - { - id: '500000200211133087', - name: 'Frank Jones', - status: 'disable' - }, - { - id: '350000199105019471', - name: 'Carol Wilson', - status: 'abnormal' - } - ] - }, - { - id: '110000198411203825', - name: 'deepseek', - logo: '\n\n\n\n\n\n\n\n\n\n\n', - status: 'abnormal', - default_llm: 'fakegpt-1.0', - api_count: 1, - key_count: 10, - keys: [ - { - id: '210000200509114330', - name: 'Jason Martin', - status: 'abnormal' - } - ] - } - ], - backup: '' - }, - msg_zh: 'default_value' - } - setModelData(mockApiResponse.data.providers) - }, []) - - useEffect(() => { - if (!modelData.length) return - - const newNodes = [ - { - id: 'service', - type: 'serviceCard', - position: { x: LAYOUT.SERVICE_NODE_X, y: LAYOUT.NODE_START_Y }, - data: {} - }, - ...modelData.map((model) => ({ - id: model.id, - type: 'modelCard', - position: calculateNodePositions(modelData)[model.id], - data: { - title: model.name, - status: model.status, - defaultModel: model.default_llm, - logo: model.logo - } - })), - ...modelData.map((model) => ({ - id: `${model.id}-keys`, - type: 'keyCard', - position: calculateNodePositions(modelData)[`${model.id}-keys`], - data: { - title: 'API Keys', - keys: model.keys.map((key, index) => ({ - keyID: key.id, - status: key.status === 'normal' ? 'enabled' : 'disable', - priority: index + 1 - })) - } - })) - ] - - const newEdges = [ - ...modelData.map((model) => ({ - id: `service-${model.id}`, - source: 'service', - target: model.id, - label: `apis(${model.api_count})`, - style: { stroke: '#ddd', cursor: 'pointer' }, - labelStyle: { fill: '#3d46f2', fontSize: 12, cursor: 'pointer' }, - labelBgStyle: { fill: 'white', fillOpacity: 0.8 }, - labelBgPadding: [4, 2], - labelShowBg: true, - type: 'smoothstep', - markerEnd: { type: 'arrow' } - })), - ...modelData.map((model) => ({ - id: `${model.id}-keys`, - source: model.id, - type: 'smoothstep', - target: `${model.id}-keys`, - animated: true, - style: { stroke: '#ddd' } - })) - ] - - setNodes(newNodes) - setEdges(newEdges) - }, [modelData]) - - const onConnect = useCallback((params: any) => setEdges((eds) => addEdge(params, eds)), [setEdges]) - - const onNodeDrag = useCallback( - (_: any, node: any) => { - if (node.type !== 'modelCard') return - - setNodes((nds) => { - return nds.map((n) => { - if (n.type === 'keyCard' && n.id === `${node.id}-keys`) { - return { - ...n, - position: { - x: LAYOUT.KEY_NODE_X, - y: node.position.y - } - } - } - return n - }) - }) - }, - [setNodes] - ) - - const onNodeDragStop = useCallback( - (_: any, node: any) => { - 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) - - 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 - } - } - } - return n - }) - }) - }, - [setNodes] - ) - - return ( -
- -
- ) -} - -export default Playground diff --git a/frontend/packages/core/tsconfig.json b/frontend/packages/core/tsconfig.json index b2dc4117..7a1a2b77 100644 --- a/frontend/packages/core/tsconfig.json +++ b/frontend/packages/core/tsconfig.json @@ -1,4 +1,3 @@ - { "compilerOptions": { "target": "ES2020", @@ -6,6 +5,7 @@ "lib": ["ES2020", "DOM", "DOM.Iterable"], "module": "ESNext", "skipLibCheck": true, + "typeRoots": ["./node_modules/@types", "../common/src/types"], /* Bundler mode */ "moduleResolution": "bundler",