mirror of
https://github.com/APIParkLab/APIPark.git
synced 2026-06-14 20:41:15 +08:00
feat: ai model detail
This commit is contained in:
@@ -46,8 +46,7 @@ const AIProviderSelect: React.FC<AIProviderSelectProps> = ({ value, onChange, st
|
||||
if (code === STATUS_CODE.SUCCESS) {
|
||||
isMounted && setProviders(data.providers)
|
||||
if (!data.providers?.length) return
|
||||
|
||||
const selectedProvider: AIProvider = value ? providers.find((p) => p.id === value) : data.providers[0]
|
||||
const selectedProvider: AIProvider = value ? data.providers.find((p) => p.id === value) : data.providers[0]
|
||||
onChange?.(selectedProvider.id, selectedProvider)
|
||||
} else {
|
||||
message.error(msg || t('Failed to fetch AI providers'))
|
||||
|
||||
@@ -12,13 +12,15 @@ 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'
|
||||
|
||||
const KeySettings: React.FC = () => {
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const { modal, message } = App.useApp()
|
||||
const [selectedProvider, setSelectedProvider] = useState<string>()
|
||||
const [searchParams] = useSearchParams()
|
||||
const [selectedProvider, setSelectedProvider] = useState<string>(searchParams.get('modelId') || '')
|
||||
const [provider, setProvider] = useState<AIProvider | undefined>()
|
||||
const [apiKeys, setApiKeys] = useState<APIKey[]>([])
|
||||
const { fetchData } = useFetch()
|
||||
|
||||
@@ -1,5 +1,6 @@
|
||||
'use client'
|
||||
|
||||
import { BasicResponse } from '@common/const/const'
|
||||
import { useFetch } from '@common/hooks/http'
|
||||
import {
|
||||
CoordinateExtent,
|
||||
@@ -20,19 +21,15 @@ import { ModelCardNode } from './components/ModelCardNode'
|
||||
import { ServiceCardNode } from './components/NodeComponents'
|
||||
import { LAYOUT } from './constants'
|
||||
import './styles.css'
|
||||
import { ModelData } from './types'
|
||||
import { AiSettingListItem, ModelData } from './types'
|
||||
|
||||
interface ApiResponse {
|
||||
data: {
|
||||
backup: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
providers: ModelData[]
|
||||
export type ApiResponse = BasicResponse<{
|
||||
backup: {
|
||||
id: string
|
||||
name: string
|
||||
}
|
||||
code: number
|
||||
success: string
|
||||
}
|
||||
providers: ModelData[]
|
||||
}>
|
||||
|
||||
const calculateNodePositions = (models: ModelData[], startY = LAYOUT.NODE_START_Y, gap = LAYOUT.NODE_GAP) => {
|
||||
return models.reduce(
|
||||
@@ -46,7 +43,7 @@ const calculateNodePositions = (models: ModelData[], startY = LAYOUT.NODE_START_
|
||||
},
|
||||
[`${model.id}-keys`]: {
|
||||
x: LAYOUT.KEY_NODE_X,
|
||||
y
|
||||
y: y + 16
|
||||
}
|
||||
}
|
||||
},
|
||||
@@ -64,7 +61,7 @@ const edgeTypes: EdgeTypes = {
|
||||
custom: CustomEdge
|
||||
}
|
||||
|
||||
const AIFlowChart = () => {
|
||||
const AIFlowChart = ({ openModal }: { openModal: (entity: AiSettingListItem) => Promise<void> }) => {
|
||||
const [modelData, setModelData] = useState<ModelData[]>([])
|
||||
const [nodes, setNodes, onNodesChange] = useNodesState<Node>([])
|
||||
const [edges, setEdges, onEdgesChange] = useEdgesState<Edge>([])
|
||||
@@ -94,7 +91,7 @@ const AIFlowChart = () => {
|
||||
type: 'serviceCard',
|
||||
position: { x: LAYOUT.SERVICE_NODE_X, y: serviceY },
|
||||
data: {
|
||||
title: 'API Service',
|
||||
title: 'API Services',
|
||||
count: modelData.length
|
||||
}
|
||||
},
|
||||
@@ -105,8 +102,10 @@ const AIFlowChart = () => {
|
||||
data: {
|
||||
title: model.name,
|
||||
status: model.status,
|
||||
defaultModel: model.default_llm,
|
||||
logo: model.logo
|
||||
defaultLlm: model.default_llm,
|
||||
logo: model.logo,
|
||||
id: model.id,
|
||||
openModal
|
||||
}
|
||||
})),
|
||||
...modelData.map((model) => ({
|
||||
@@ -114,7 +113,7 @@ const AIFlowChart = () => {
|
||||
type: 'keyCard',
|
||||
position: positions[`${model.id}-keys`],
|
||||
data: {
|
||||
title: 'API Keys',
|
||||
title: '',
|
||||
keys: (model.keys || []).map((key, index) => ({
|
||||
id: key.id,
|
||||
status: key.status,
|
||||
@@ -138,6 +137,7 @@ const AIFlowChart = () => {
|
||||
source: model.id,
|
||||
target: `${model.id}-keys`,
|
||||
label: `${model.key_count} keys`,
|
||||
data: { id: model.id },
|
||||
animated: true
|
||||
}))
|
||||
]
|
||||
@@ -147,8 +147,8 @@ const AIFlowChart = () => {
|
||||
}, [modelData])
|
||||
|
||||
const calculateExtent = useCallback(() => {
|
||||
const left = LAYOUT.SERVICE_NODE_X - 100
|
||||
const right = LAYOUT.KEY_NODE_X + 100
|
||||
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 [
|
||||
@@ -207,7 +207,7 @@ const AIFlowChart = () => {
|
||||
...n,
|
||||
position: {
|
||||
x: LAYOUT.KEY_NODE_X,
|
||||
y: LAYOUT.NODE_START_Y + index * LAYOUT.NODE_GAP
|
||||
y: LAYOUT.NODE_START_Y + index * LAYOUT.NODE_GAP + 16
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@ 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 { AiSettingListItem } from './AiSettingList'
|
||||
import { AiSettingListItem } from './types'
|
||||
|
||||
const CardBox = memo(
|
||||
({
|
||||
@@ -92,11 +92,11 @@ const ModelCardArea = ({
|
||||
</>
|
||||
)
|
||||
}
|
||||
interface AIUnconfigureProps {
|
||||
interface AIUnConfigureProps {
|
||||
openModal: (entity: AiSettingListItem) => Promise<void>
|
||||
}
|
||||
|
||||
const AIUnconfigure: FC<AIUnconfigureProps> = ({ openModal }) => {
|
||||
const AIUnConfigure: FC<AIUnConfigureProps> = ({ openModal }) => {
|
||||
const { message } = App.useApp()
|
||||
const { fetchData } = useFetch()
|
||||
const [aiSettingList, setAiSettingList] = useState<AiSettingListItem[]>([])
|
||||
@@ -141,7 +141,7 @@ const AIUnconfigure: FC<AIUnconfigureProps> = ({ openModal }) => {
|
||||
<div>
|
||||
{aiSettingList.filter((item) => !item.configured).length > 0 && (
|
||||
<>
|
||||
<ModelCardArea modelList={aiSettingList.filter((item) => !item.configured) || []} />
|
||||
<ModelCardArea openModal={openModal} modelList={aiSettingList.filter((item) => !item.configured) || []} />
|
||||
</>
|
||||
)}
|
||||
</div>
|
||||
@@ -152,4 +152,4 @@ const AIUnconfigure: FC<AIUnconfigureProps> = ({ openModal }) => {
|
||||
)
|
||||
}
|
||||
|
||||
export default AIUnconfigure
|
||||
export default AIUnConfigure
|
||||
|
||||
@@ -9,7 +9,7 @@ import { App, Tabs } from 'antd'
|
||||
import { useRef } from 'react'
|
||||
import AIFlowChart from './AIFlowChart'
|
||||
import AiSettingModalContent, { AiSettingModalContentHandle } from './AiSettingModal'
|
||||
import AIUnconfigure from './AIUnconfigure'
|
||||
import AIUnConfigure from './AIUnconfigure'
|
||||
import { AiProviderConfig, AiSettingListItem } from './types'
|
||||
|
||||
const AiSettingList = () => {
|
||||
@@ -19,6 +19,7 @@ const AiSettingList = () => {
|
||||
const { setAiConfigFlushed, accessData } = useGlobalContext()
|
||||
|
||||
const openModal = async (entity: AiSettingListItem) => {
|
||||
console.log(entity)
|
||||
message.loading($t(RESPONSE_TIPS.loading))
|
||||
const { code, data, msg } = await fetchData<BasicResponse<{ provider: AiProviderConfig }>>('ai/provider/config', {
|
||||
method: 'GET',
|
||||
@@ -87,12 +88,12 @@ const AiSettingList = () => {
|
||||
{
|
||||
key: 'flow',
|
||||
label: $t('已设置'),
|
||||
children: <AIFlowChart />
|
||||
children: <AIFlowChart openModal={openModal} />
|
||||
},
|
||||
{
|
||||
key: 'config',
|
||||
label: $t('未设置'),
|
||||
children: <div className="overflow-auto flex-grow">{<AIUnconfigure openModal={openModal} />}</div>
|
||||
children: <div className="overflow-auto flex-grow">{<AIUnConfigure openModal={openModal} />}</div>
|
||||
}
|
||||
]}
|
||||
/>
|
||||
|
||||
@@ -113,7 +113,7 @@ const AiSettingModalContent = forwardRef<AiSettingModalContentHandle, AiSettingM
|
||||
label: (
|
||||
<div className="flex items-center gap-[10px]">
|
||||
<span>{x.id}</span>
|
||||
{x?.scopes?.map((s) => <Tag>{s?.toLocaleUpperCase()}</Tag>)}
|
||||
{x?.scopes?.map((s) => <Tag key={s}>{s?.toLocaleUpperCase()}</Tag>)}
|
||||
</div>
|
||||
)
|
||||
}))}
|
||||
|
||||
@@ -31,7 +31,7 @@ export default function CustomEdge({
|
||||
{label && (
|
||||
<EdgeLabelRenderer>
|
||||
<a
|
||||
href={`/aiSetting/model?modelId=${modelId}`}
|
||||
href={`${label?.toString().includes('apis') ? '/aiapis' : '/keysetting'}?modelId=${modelId}`}
|
||||
target="_blank"
|
||||
style={{
|
||||
position: 'absolute',
|
||||
|
||||
@@ -22,7 +22,7 @@ export const KeyStatusNode: React.FC<{ data: KeyStatusNodeData }> = ({ data }) =
|
||||
style={{ border: '1px solid var(--border-color)' }}
|
||||
>
|
||||
<Handle type="target" position={Position.Left} />
|
||||
<div className="flex flex-col gap-2">
|
||||
<div className="flex flex-col">
|
||||
<div className="text-sm text-gray-900">{title}</div>
|
||||
<div
|
||||
className="flex gap-1 w-full"
|
||||
|
||||
@@ -2,22 +2,23 @@ import { Icon } from '@iconify/react'
|
||||
import { Handle, Position } from '@xyflow/react'
|
||||
import { t } from 'i18next'
|
||||
import React from 'react'
|
||||
import { ModelStatus } from '../types'
|
||||
import { AiSettingListItem, ModelStatus } from '../types'
|
||||
|
||||
interface ModelCardData {
|
||||
title: string
|
||||
status: ModelStatus
|
||||
logo: string
|
||||
defaultModel: string
|
||||
defaultLlm: string
|
||||
}
|
||||
|
||||
type ModelCardNodeData = ModelCardData & {
|
||||
id: string
|
||||
position: { x: number; y: number }
|
||||
openModal?: (entity: AiSettingListItem) => Promise<void>
|
||||
}
|
||||
|
||||
export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) => {
|
||||
const { title, status, defaultModel, logo } = data
|
||||
const { title, status, defaultLlm, logo } = data
|
||||
return (
|
||||
<div
|
||||
className="node-card bg-white rounded-lg shadow-sm p-4 min-w-[280px] group"
|
||||
@@ -46,13 +47,13 @@ export const ModelCardNode: React.FC<{ data: ModelCardNodeData }> = ({ data }) =
|
||||
<Icon
|
||||
icon="mdi:cog"
|
||||
className="text-xl text-gray-400 cursor-pointer hover:text-[--primary-color]"
|
||||
onClick={() => console.log('Default:', data.id)}
|
||||
onClick={() => data.openModal?.({ id: data.id, defaultLlm: defaultLlm })}
|
||||
/>
|
||||
</div>
|
||||
</div>
|
||||
<div className="mt-2 text-sm text-gray-500">
|
||||
{t('默认:')}
|
||||
{defaultModel}
|
||||
{defaultLlm}
|
||||
</div>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
@@ -11,7 +11,7 @@ export const ServiceCardNode: React.FC<NodeProps> = () => {
|
||||
<Handle type="source" position={Position.Right} />
|
||||
<div className="flex flex-col gap-2 items-center">
|
||||
<Icon icon="mdi:robot" className="text-3xl text-[--primary-color]" />
|
||||
<span className="text-base text-gray-900">AI Service</span>
|
||||
<span className="text-base text-gray-900">AI Services</span>
|
||||
</div>
|
||||
</div>
|
||||
)
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
export const LAYOUT = {
|
||||
SERVICE_NODE_X: 50,
|
||||
SERVICE_NODE_X: 0,
|
||||
NODE_START_Y: 20,
|
||||
NODE_GAP: 120,
|
||||
MODEL_NODE_X: 500,
|
||||
|
||||
@@ -12,15 +12,16 @@ 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'
|
||||
|
||||
const KeySettings: React.FC = () => {
|
||||
const pageListRef = useRef<ActionType>(null)
|
||||
const { modal, message } = App.useApp()
|
||||
const [selectedProvider, setSelectedProvider] = useState<string>()
|
||||
const [searchParams] = useSearchParams()
|
||||
const [selectedProvider, setSelectedProvider] = useState<string>(searchParams.get('modelId') || '')
|
||||
const [provider, setProvider] = useState<AIProvider | undefined>()
|
||||
const [apiKeys, setApiKeys] = useState<APIKey[]>([])
|
||||
const { fetchData } = useFetch()
|
||||
const [searchWord, setSearchWord] = useState<string>('')
|
||||
const [total, setTotal] = useState<number>(0)
|
||||
|
||||
Reference in New Issue
Block a user