feat: APO node select quickly

pull/17608/head
keting lu 1 year ago
parent ab453fa9d6
commit fbbfaddd85

@ -2,14 +2,14 @@ import type { ApoToolTypeInfo } from './types'
export const initApoToolsEntry: Record<ApoToolTypeInfo, any> = { export const initApoToolsEntry: Record<ApoToolTypeInfo, any> = {
select: { select: {
label: 'APO异常检测', label: 'APO平台查询可观测性数据',
description: 'APO异常检测分析描述', description: '查询APO平台可观测性数据, 用于进一步分析',
icon: '', icon: '',
type: 'select', type: 'select',
}, },
analysis: { analysis: {
label: 'APO查询检测', label: 'APO数据异常检测&关联',
description: 'APO查询检测', description: '输入可观测性数据, 通过相关算法识别出现异常数据、或者直接对数据进行关联汇总。',
icon: '', icon: '',
type: 'analysis', type: 'analysis',
}, },

@ -0,0 +1,152 @@
import { memo, useCallback, useEffect, useState } from 'react'
import { BlockEnum } from '../../types'
import type { ToolDefaultValue } from '../../block-selector/types'
import type { ApoToolTypeInfo } from '../types'
import Input from '../../../base/input'
import { fetchApoTools } from '@/service/tools'
import { useTranslation } from 'react-i18next'
import ToolTrialRun from './tool-trial-run'
import ParametersInfo from './parameters-info'
import cn from '@/utils/classnames'
import { debounce } from 'lodash-es'
import { useGetLanguage } from '@/context/i18n'
type ApoToolsPreviewProps = {
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void;
apoToolType: ApoToolTypeInfo | null;
hidePopover: any
}
const ApoToolsPreview = ({ onSelect, apoToolType, hidePopover }: ApoToolsPreviewProps) => {
const language = useGetLanguage()
const { t } = useTranslation()
const [toolDetail, setToolDetail] = useState()
const [tools, setTools] = useState<any>([])
const [provider, setProvider] = useState()
const [searchText, setSearchText] = useState<string>()
const getAllTools = async () => {
const tools = await fetchApoTools(apoToolType, searchText)
// setSearchText('')
setProvider(tools[0])
setTools(tools[0]?.tools)
}
const handlePopoverOpen = useCallback((item) => {
setToolDetail(item)
}, [])
const convertMetricsListToMenuItems = useCallback(() => {
return tools.map((item, index) => (
<div
key={index}
className={cn(
'flex items-center justify-between pl-3 pr-1 w-full rounded-lg hover:bg-state-base-hover cursor-pointer select-none',
item.name === toolDetail?.name && 'bg-state-base-active text-red-500',
)}
onMouseEnter={() => handlePopoverOpen(item)}
onClick={() => {
const params: Record<string, string> = {}
if (item.parameters) {
item.parameters.forEach((param) => {
params[param.name] = ''
})
}
onSelect(BlockEnum.Tool, {
provider_id: provider.id,
provider_type: provider.type,
provider_name: provider.name,
tool_name: item.name,
tool_label: item.label[language],
title: item.label[language],
is_team_authorization: provider.is_team_authorization,
output_schema: item.output_schema,
paramSchemas: item.parameters,
params,
})
hidePopover()
}}
>
<div className="flex grow items-center h-8">
{/* <BlockIcon
className='shrink-0'
type={BlockEnum.Tool}
toolIcon={item.icon}
/> */}
<div className="ml-2 text-sm text-text-primary flex-1 w-0 grow truncate">
{item.label[language]}
</div>
</div>
</div>
))
}, [tools, handlePopoverOpen, toolDetail])
useEffect(() => {
const fetchTools = debounce(() => {
if (apoToolType) getAllTools()
}, 500) // 500ms 防抖
fetchTools()
return () => {
fetchTools.cancel() // 组件卸载时清理
}
}, [apoToolType, searchText])
return (
<div>
<div className="flex">
{/* left */}
<div className="w-[300px]">
<div className="mb-3">
<Input
value={searchText}
onChange={e => setSearchText(e.target.value)}
/>
</div>
<div className="p-0.5 space-y-0.5 rounded-[10px]">
{tools?.length > 0 && convertMetricsListToMenuItems()}
</div>
</div>
<div className="flex-1">
{toolDetail && (
<div className="px-4">
<div>{toolDetail.label[language]}</div>
<div className="text-xs text-text-tertiary">
<div className="pt-2">
<>{toolDetail.description[language]}</>
</div>
<div className="pt-2">
{apoToolType === 'select' ? (
<>
{['cpu', 'network', 'memory'].includes(toolDetail?.display.type) && <div className="pb-2">{toolDetail.display.unit}</div>}
{toolDetail?.display?.type && <div>{t(`apo.displayType.${toolDetail?.display?.type}`)}</div>}
<div className="px-4 py-2">
<div className="h-[0.5px] divider-subtle" />
</div>
<div className="pb-2">
<ToolTrialRun infoSchemas={toolDetail.parameters} />
</div>
</>
) : (
<div className="flex">
<span></span>
<div>
{toolDetail?.parameters.map(parameter => (
<ParametersInfo
key={parameter.name}
parameter={parameter}
/>
))}
</div>
</div>
)}
</div>
</div>
</div>
)}
</div>
</div>
</div>
)
}
export default memo(ApoToolsPreview)

@ -0,0 +1,35 @@
import { memo, useContext } from 'react'
import I18n from '@/context/i18n'
import { getLanguage } from '@/i18n/language'
import { useTranslation } from 'react-i18next'
const ParametersInfo = ({ parameter }) => {
const { locale } = useContext(I18n)
const language = getLanguage(locale)
const { t } = useTranslation()
const getType = (type: string) => {
if (type === 'number-input')
return t('tools.setBuiltInTools.number')
if (type === 'text-input')
return t('tools.setBuiltInTools.string')
if (type === 'file')
return t('tools.setBuiltInTools.file')
return type
}
return <div>
<div className='flex items-center gap-2'>
<div className='text-text-secondary code-sm-semibold'>{parameter.label[language]}</div>
<div className='text-text-tertiary system-xs-regular'>
{getType(parameter.type)}
</div>
{parameter.required && (
<div className='text-text-warning-secondary system-xs-medium'>{t('tools.setBuiltInTools.required')}</div>
)}
</div>
{parameter.human_description && (
<div className='mt-0.5 text-text-tertiary system-xs-regular'>
{parameter.human_description?.[language]}
</div>
)}
</div>
}
export default memo(ParametersInfo)

@ -0,0 +1,42 @@
import { memo, useEffect, useState } from 'react'
import Form from '../../nodes/_base/components/before-run-form/form'
import { InputVarType } from '../../types'
import ParametersInfo from './parameters-info'
import { useTranslation } from 'react-i18next'
import { useGetLanguage } from '@/context/i18n'
const varTypeToInputVarType = (type: string) => {
if(type === 'string')
return InputVarType.textInput
else if (type === 'file')
return InputVarType.singleFile
return type
}
const ToolTrialRun = ({ infoSchemas }) => {
const [formValues, setFormValues] = useState(null)
const [formInputs, setFormInputs] = useState([])
const language = useGetLanguage()
const { t } = useTranslation()
useEffect(() => {
const formValues = infoSchemas?.reduce((acc, item) => {
acc[item.name] = null
return acc
}, {})
const formInputs = infoSchemas.map(item => ({
// label: formLable(item),
label: item.label[language],
require: item.require,
type: varTypeToInputVarType(item.type),
variable: item.name,
customLabel: <ParametersInfo parameter={item} />,
}))
setFormValues(formValues)
setFormInputs(formInputs)
}, [infoSchemas])
return <>
<div className='py-2 text-text-primary system-sm-semibold-uppercase'>{t('tools.setBuiltInTools.parameters')}</div>
<Form inputs={formInputs} values={formValues} onChange={newValues => setFormValues(newValues) }
></Form>
</>
}
export default memo(ToolTrialRun)

@ -1,70 +1,64 @@
import Tooltip from '@/app/components/base/tooltip' import type { BlockEnum } from '../types'
import BlockIcon from '../block-icon'
import { BlockEnum } from '../types'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
import { initApoToolsEntry } from '../apo/constant' import { initApoToolsEntry } from '../apo/constant'
import AINodeRecommend from '@/app/components/workflow/apo/ai-node-recommend'
import { useState } from 'react' import { useState } from 'react'
import type { ToolDefaultValue } from './types' import type { ToolDefaultValue } from './types'
import { Popover } from 'antd'
import ApoToolsPreview from '../apo/tool-preview/apo-tools-preview'
import type { ApoToolTypeInfo } from '../apo/types'
type APOToolsProps = { type APOToolsProps = {
searchText: string; searchText: string;
onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void onSelect: (type: BlockEnum, tool?: ToolDefaultValue) => void
} }
const APOTools = ({ searchText, onSelect }: APOToolsProps) => { const APOTools = ({ searchText, onSelect }: APOToolsProps) => {
const [apoToolType, setApoToolType] = useState() const [openKey, setOpenKey] = useState({
const [showRecommendModal, setShowRecommendModal] = useState() select: false,
const handleSelect = (type: BlockEnum) => { analysis: false,
onSelect(type) })
setShowRecommendModal(false) const hide = (key: ApoToolTypeInfo) => {
setOpenKey(prev => ({
...prev,
[key]: false,
}))
}
const handleOpenChange = (key: ApoToolTypeInfo, newOpen: boolean) => {
setOpenKey(prev => ({
...prev,
[key]: newOpen,
}))
} }
return ( return (
<> <>
<div className="mb-1 last-of-type:mb-0"> <div className="mb-1 last-of-type:mb-0">
{Object.entries(initApoToolsEntry).map(([key, tool]) => ( {Object.entries(initApoToolsEntry).map(([key, tool]) => (
<Tooltip <Popover
key={key} key={key}
position="right" placement='rightTop'
popupClassName="w-[200px]" trigger={['click']}
popupContent={ open={openKey[key]}
<div> onOpenChange={newOpen => handleOpenChange(key, newOpen)}
<BlockIcon content={
size="md" <ApoToolsPreview onSelect={onSelect} apoToolType={key} hidePopover={() => hide(key)}/>
className="mb-2"
type={BlockEnum.Tool}
toolIcon={tool.icon}
/>
<div className={cn('grow text-sm text-gray-900 truncate')}>
{tool.label}
</div>
<div className="text-xs text-gray-700 leading-[18px]">
{tool.description}
</div>
{/* <div className={cn('grow text-sm text-gray-900 truncate')}>{tool.label[language]}</div>
<div className='text-xs text-gray-700 leading-[18px]'>{tool.description[language]}</div> */}
</div>
} }
> >
<div <div
key={tool.type} key={tool.type}
className="flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer" className="flex items-center px-3 w-full h-8 rounded-lg hover:bg-state-base-hover cursor-pointer"
onClick={() => {
setApoToolType(tool.type)
setShowRecommendModal(true)
}}
> >
<BlockIcon {/* <BlockIcon
className="mr-2 shrink-0" className="mr-2 shrink-0"
type={BlockEnum.Tool} type={BlockEnum.Tool}
toolIcon={tool.icon} toolIcon={tool.icon}
/> /> */}
<div className={cn('grow text-sm text-gray-900 truncate')}> <div className={cn('grow text-sm text-gray-900 truncate')}>
{tool.label} {tool.label}
</div> </div>
</div> </div>
</Tooltip> </Popover>
))} ))}
</div> </div>
<AINodeRecommend {/* <AINodeRecommend
onSelect={handleSelect} onSelect={handleSelect}
apoToolType={apoToolType} apoToolType={apoToolType}
showRecommendModal={showRecommendModal} showRecommendModal={showRecommendModal}
@ -72,7 +66,7 @@ const APOTools = ({ searchText, onSelect }: APOToolsProps) => {
setApoToolType(null) setApoToolType(null)
setShowRecommendModal(false) setShowRecommendModal(false)
}} }}
/> /> */}
</> </>
) )
} }

@ -1,5 +1,5 @@
'use client' 'use client'
import type { FC } from 'react' import type { FC, ReactNode } from 'react'
import React, { useCallback } from 'react' import React, { useCallback } from 'react'
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
import produce from 'immer' import produce from 'immer'
@ -25,13 +25,14 @@ import { BubbleX } from '@/app/components/base/icons/src/vender/line/others'
import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants' import { FILE_EXTS } from '@/app/components/base/prompt-editor/constants'
import cn from '@/utils/classnames' import cn from '@/utils/classnames'
interface Props { type Props = {
payload: InputVar payload: InputVar
value: any value: any
onChange: (value: any) => void onChange: (value: any) => void
className?: string className?: string
autoFocus?: boolean autoFocus?: boolean
inStepRun?: boolean inStepRun?: boolean
customLabel?: ReactNode
} }
const FormItem: FC<Props> = ({ const FormItem: FC<Props> = ({
@ -41,6 +42,7 @@ const FormItem: FC<Props> = ({
className, className,
autoFocus, autoFocus,
inStepRun = false, inStepRun = false,
customLabel,
}) => { }) => {
const { t } = useTranslation() const { t } = useTranslation()
const { type } = payload const { type } = payload
@ -96,12 +98,13 @@ const FormItem: FC<Props> = ({
const isIterator = type === InputVarType.iterator const isIterator = type === InputVarType.iterator
return ( return (
<div className={cn(className)}> <div className={cn(className)}>
{!isArrayLikeType && ( {
customLabel || <>{!isArrayLikeType && (
<div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'> <div className='h-6 mb-1 flex items-center gap-1 text-text-secondary system-sm-semibold'>
<div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div> <div className='truncate'>{typeof payload.label === 'object' ? nodeKey : payload.label}</div>
{!payload.required && <span className='text-text-tertiary system-xs-regular'>{t('workflow.panel.optional')}</span>} {!payload.required && <span className='text-text-tertiary system-xs-regular'>{t('workflow.panel.optional')}</span>}
</div> </div>
)} )}</>}
<div className='grow'> <div className='grow'>
{ {
type === InputVarType.textInput && ( type === InputVarType.textInput && (

@ -9,10 +9,10 @@ import { InputVarType } from '@/app/components/workflow/types'
import AddButton from '@/app/components/base/button/add-button' import AddButton from '@/app/components/base/button/add-button'
import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants' import { RETRIEVAL_OUTPUT_STRUCT } from '@/app/components/workflow/constants'
export interface Props { export type Props = {
className?: string className?: string
label?: string label?: string
inputs: InputVar[] inputs: InputVar[] | any
values: Record<string, string> values: Record<string, string>
onChange: (newValues: Record<string, any>) => void onChange: (newValues: Record<string, any>) => void
} }
@ -85,6 +85,7 @@ const Form: FC<Props> = ({
payload={input} payload={input}
value={values[input.variable]} value={values[input.variable]}
onChange={handleChange(input.variable)} onChange={handleChange(input.variable)}
customLabel={input.customLabel}
/> />
) )
})} })}

@ -6,6 +6,16 @@ const translation = {
chart: { chart: {
chartTitle: 'Chart Display', chartTitle: 'Chart Display',
}, },
displayType: {
cpu: 'CPU metrics',
network: 'Network metrics',
memory: 'Memory metrics',
topology: 'Topology structure',
alert: 'Alert events',
log: 'Log data',
custom: 'Custom query return data',
},
} }
export default translation export default translation

@ -6,6 +6,16 @@ const translation = {
chart: { chart: {
chartTitle: '图表渲染', chartTitle: '图表渲染',
}, },
displayType: {
cpu: 'CPU指标',
network: '网络指标',
memory: '内存指标',
topology: '拓扑结构',
alert: '告警事件',
log: '日志数据',
custom: '自定义查询返回数据',
},
} }
export default translation export default translation

Loading…
Cancel
Save