feat: ai model detail

This commit is contained in:
scarqin
2024-12-30 14:59:22 +08:00
parent 5a2b509d68
commit 87b70a3faf
12 changed files with 47 additions and 43 deletions
@@ -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)