feat(web): agent support add tools
This commit is contained in:
@@ -18,7 +18,9 @@ import type {
|
|||||||
Variable,
|
Variable,
|
||||||
MemoryConfig,
|
MemoryConfig,
|
||||||
AiPromptModalRef,
|
AiPromptModalRef,
|
||||||
Source
|
Source,
|
||||||
|
ToolModalRef,
|
||||||
|
ToolOption
|
||||||
} from './types'
|
} from './types'
|
||||||
import type { Model } from '@/views/ModelManagement/types'
|
import type { Model } from '@/views/ModelManagement/types'
|
||||||
import { getModelList } from '@/api/models';
|
import { getModelList } from '@/api/models';
|
||||||
@@ -31,6 +33,8 @@ import { memoryConfigListUrl } from '@/api/memory'
|
|||||||
import CustomSelect from '@/components/CustomSelect'
|
import CustomSelect from '@/components/CustomSelect'
|
||||||
import aiPrompt from '@/assets/images/application/aiPrompt.png'
|
import aiPrompt from '@/assets/images/application/aiPrompt.png'
|
||||||
import AiPromptModal from './components/AiPromptModal'
|
import AiPromptModal from './components/AiPromptModal'
|
||||||
|
import ToolModal from './components/ToolModal'
|
||||||
|
import ToolList from './components/ToolList'
|
||||||
|
|
||||||
const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => {
|
const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => {
|
||||||
return (
|
return (
|
||||||
@@ -47,12 +51,12 @@ const LabelWrapper: FC<{title: string, className?: string; children?: ReactNode}
|
|||||||
</div>
|
</div>
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
const SwitchWrapper: FC<{ title: string, desc: string, name: string }> = ({ title, desc, name }) => {
|
const SwitchWrapper: FC<{ title: string, desc?: string, name: string | string[]; needTransition?: boolean; }> = ({ title, desc, name, needTransition = true }) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
return (
|
return (
|
||||||
<div className="rb:flex rb:items-center rb:justify-between">
|
<div className="rb:flex rb:items-center rb:justify-between">
|
||||||
<LabelWrapper title={t(`application.${title}`)}>
|
<LabelWrapper title={needTransition ? t(`application.${title}`) : title}>
|
||||||
<DescWrapper desc={t(`application.${desc}`)} className="rb:mt-2" />
|
{desc && <DescWrapper desc={needTransition ? t(`application.${desc}`) : desc} className="rb:mt-2" />}
|
||||||
</LabelWrapper>
|
</LabelWrapper>
|
||||||
<Form.Item
|
<Form.Item
|
||||||
name={name}
|
name={name}
|
||||||
@@ -100,11 +104,11 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
const [formData, setFormData] = useState<{
|
const [formData, setFormData] = useState<{
|
||||||
default_model_config_id?: string,
|
default_model_config_id?: string,
|
||||||
model_parameters?: Config['model_parameters'],
|
model_parameters?: Config['model_parameters'],
|
||||||
|
tools: ToolOption[],
|
||||||
} | null>(null)
|
} | null>(null)
|
||||||
const values = Form.useWatch<{
|
const values = Form.useWatch<{
|
||||||
memoryEnabled: boolean;
|
memoryEnabled: boolean;
|
||||||
memory_content?: string | number;
|
memory_content?: string | number;
|
||||||
webSearch: boolean;
|
|
||||||
} & Config>([], form)
|
} & Config>([], form)
|
||||||
|
|
||||||
const [knowledgeConfig, setKnowledgeConfig] = useState<KnowledgeConfig>({ knowledge_bases: [] })
|
const [knowledgeConfig, setKnowledgeConfig] = useState<KnowledgeConfig>({ knowledge_bases: [] })
|
||||||
@@ -149,17 +153,21 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
setLoading(true)
|
setLoading(true)
|
||||||
getApplicationConfig(id as string).then(res => {
|
getApplicationConfig(id as string).then(res => {
|
||||||
const response = res as Config
|
const response = res as Config
|
||||||
setData(response)
|
setData({
|
||||||
|
...response,
|
||||||
|
tools: Array.isArray(response.tools) ? response.tools : []
|
||||||
|
})
|
||||||
const { memory, tools } = response
|
const { memory, tools } = response
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
...response,
|
...response,
|
||||||
memoryEnabled: memory?.enabled || false,
|
memoryEnabled: memory?.enabled || false,
|
||||||
memory_content: memory?.memory_content ? Number(memory?.memory_content) : undefined,
|
memory_content: memory?.memory_content ? Number(memory?.memory_content) : undefined,
|
||||||
webSearch: tools?.web_search?.enabled || false,
|
tools: Array.isArray(tools) ? tools : []
|
||||||
})
|
})
|
||||||
setFormData({
|
setFormData({
|
||||||
default_model_config_id: response.default_model_config_id,
|
default_model_config_id: response.default_model_config_id,
|
||||||
model_parameters: response.model_parameters || {},
|
model_parameters: response.model_parameters || {},
|
||||||
|
tools: Array.isArray(tools) ? tools : []
|
||||||
})
|
})
|
||||||
if (response?.knowledge_retrieval?.knowledge_bases?.length) {
|
if (response?.knowledge_retrieval?.knowledge_bases?.length) {
|
||||||
getDefaultKnowledgeList(response)
|
getDefaultKnowledgeList(response)
|
||||||
@@ -260,8 +268,9 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
// 保存Agent配置
|
// 保存Agent配置
|
||||||
const handleSave = (flag = true) => {
|
const handleSave = (flag = true) => {
|
||||||
if (!isSave || !data) return Promise.resolve()
|
if (!isSave || !data) return Promise.resolve()
|
||||||
const { memoryEnabled, memory_content, webSearch, ...rest } = values
|
const { memoryEnabled, memory_content, ...rest } = values
|
||||||
const { knowledge_bases = [], ...knowledgeRest } = knowledgeConfig || {}
|
const { knowledge_bases = [], ...knowledgeRest } = knowledgeConfig || {}
|
||||||
|
|
||||||
|
|
||||||
// 从原数据中获取memory的其他必要属性
|
// 从原数据中获取memory的其他必要属性
|
||||||
const originalMemory = data.memory || ({} as MemoryConfig)
|
const originalMemory = data.memory || ({} as MemoryConfig)
|
||||||
@@ -285,15 +294,10 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
...(item.config || {})
|
...(item.config || {})
|
||||||
}))
|
}))
|
||||||
} as KnowledgeConfig : null,
|
} as KnowledgeConfig : null,
|
||||||
tools: {
|
tools: toolList
|
||||||
web_search: {
|
|
||||||
enabled: webSearch,
|
|
||||||
config: {
|
|
||||||
web_search: webSearch
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('params', rest, params)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
saveAgentConfig(data.app_id, params)
|
saveAgentConfig(data.app_id, params)
|
||||||
@@ -342,6 +346,19 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
const updatePrompt = (value: string) => {
|
const updatePrompt = (value: string) => {
|
||||||
form.setFieldValue('system_prompt', value)
|
form.setFieldValue('system_prompt', value)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const toolModalRef = useRef<ToolModalRef>(null)
|
||||||
|
const [toolList, setToolList] = useState<ToolOption[]>([])
|
||||||
|
const handleAddTool = () => {
|
||||||
|
toolModalRef.current?.handleOpen()
|
||||||
|
}
|
||||||
|
const updateTools = (tool: ToolOption) => {
|
||||||
|
const tools = [...toolList, tool]
|
||||||
|
setToolList(tools)
|
||||||
|
form.setFieldValue('tools', tools)
|
||||||
|
}
|
||||||
|
|
||||||
|
console.log('toolList', toolList)
|
||||||
return (
|
return (
|
||||||
<>
|
<>
|
||||||
{loading && <Spin fullscreen></Spin>}
|
{loading && <Spin fullscreen></Spin>}
|
||||||
@@ -410,14 +427,12 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
data={data?.variables}
|
data={data?.variables}
|
||||||
onUpdate={setVariableList}
|
onUpdate={setVariableList}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
{/* 工具配置 */}
|
{/* 工具配置 */}
|
||||||
<Card title={t('application.toolConfiguration')}>
|
<ToolList
|
||||||
<Space size={24} direction='vertical' style={{ width: '100%' }}>
|
data={data?.tools || []}
|
||||||
<SwitchWrapper title="webSearch" desc="webSearchDesc" name="webSearch" />
|
onUpdate={setToolList}
|
||||||
{/* <SwitchWrapper title="codeExecutor" desc="codeExecutorDesc" name="codeExecutor" />
|
/>
|
||||||
<SwitchWrapper title="imageGeneration" desc="imageGenerationDesc" name="imageGeneration" /> */}
|
|
||||||
</Space>
|
|
||||||
</Card>
|
|
||||||
</Space>
|
</Space>
|
||||||
</Form>
|
</Form>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -454,6 +469,10 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
defaultModel={defaultModel}
|
defaultModel={defaultModel}
|
||||||
refresh={updatePrompt}
|
refresh={updatePrompt}
|
||||||
/>
|
/>
|
||||||
|
<ToolModal
|
||||||
|
ref={toolModalRef}
|
||||||
|
refresh={updateTools}
|
||||||
|
/>
|
||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -31,10 +31,6 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
|||||||
setEditConfig({ ...(data || {}) })
|
setEditConfig({ ...(data || {}) })
|
||||||
const knowledge_bases = [...(data.knowledge_bases || [])]
|
const knowledge_bases = [...(data.knowledge_bases || [])]
|
||||||
setKnowledgeList(knowledge_bases)
|
setKnowledgeList(knowledge_bases)
|
||||||
onUpdate(prev => ({
|
|
||||||
...prev,
|
|
||||||
knowledge_bases: knowledge_bases,
|
|
||||||
}))
|
|
||||||
}
|
}
|
||||||
}, [data])
|
}, [data])
|
||||||
|
|
||||||
@@ -47,10 +43,10 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
|||||||
const handleDeleteKnowledge = (id: string) => {
|
const handleDeleteKnowledge = (id: string) => {
|
||||||
const list = knowledgeList.filter(item => item.id !== id)
|
const list = knowledgeList.filter(item => item.id !== id)
|
||||||
setKnowledgeList([...list])
|
setKnowledgeList([...list])
|
||||||
onUpdate(prev => ({
|
onUpdate({
|
||||||
...prev,
|
...editConfig,
|
||||||
knowledge_bases: [...list],
|
knowledge_bases: [...list],
|
||||||
}))
|
})
|
||||||
}
|
}
|
||||||
const handleEditKnowledge = (item: KnowledgeBase) => {
|
const handleEditKnowledge = (item: KnowledgeBase) => {
|
||||||
knowledgeConfigModalRef.current?.handleOpen(item)
|
knowledgeConfigModalRef.current?.handleOpen(item)
|
||||||
@@ -69,10 +65,10 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
|||||||
list = [...values as KnowledgeBase[]]
|
list = [...values as KnowledgeBase[]]
|
||||||
}
|
}
|
||||||
setKnowledgeList([...list])
|
setKnowledgeList([...list])
|
||||||
onUpdate(prev => ({
|
onUpdate({
|
||||||
...prev,
|
...editConfig,
|
||||||
knowledge_bases: [...list],
|
knowledge_bases: [...list],
|
||||||
}))
|
})
|
||||||
} else if (type === 'knowledgeConfig') {
|
} else if (type === 'knowledgeConfig') {
|
||||||
const index = knowledgeList.findIndex(item => item.id === (values as KnowledgeBase).kb_id)
|
const index = knowledgeList.findIndex(item => item.id === (values as KnowledgeBase).kb_id)
|
||||||
const list = [...knowledgeList]
|
const list = [...knowledgeList]
|
||||||
@@ -81,18 +77,19 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
|||||||
config: {...values as KnowledgeConfigForm}
|
config: {...values as KnowledgeConfigForm}
|
||||||
}
|
}
|
||||||
setKnowledgeList([...list])
|
setKnowledgeList([...list])
|
||||||
onUpdate(prev => ({
|
onUpdate({
|
||||||
...prev,
|
...editConfig,
|
||||||
knowledge_bases: [...list],
|
knowledge_bases: [...list],
|
||||||
}))
|
})
|
||||||
} else if (type === 'rerankerConfig') {
|
} else if (type === 'rerankerConfig') {
|
||||||
setEditConfig(prev => ({ ...prev, ...(values as RerankerConfig) }))
|
const rerankerValues = values as RerankerConfig
|
||||||
onUpdate(prev => ({
|
setEditConfig(prev => ({ ...prev, ...rerankerValues }))
|
||||||
...prev,
|
onUpdate({
|
||||||
...values,
|
...editConfig,
|
||||||
reranker_id: values.rerank_model ? values.reranker_id : undefined,
|
...rerankerValues,
|
||||||
reranker_top_k: values.rerank_model ? values.reranker_top_k : undefined,
|
reranker_id: rerankerValues.rerank_model ? rerankerValues.reranker_id : undefined,
|
||||||
}))
|
reranker_top_k: rerankerValues.rerank_model ? rerankerValues.reranker_top_k : undefined,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return (
|
return (
|
||||||
@@ -102,8 +99,8 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
|||||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={() => handleKnowledgeConfig()}>{t('application.globalConfig')}</Button>
|
<Button style={{padding: '0 8px', height: '24px'}} onClick={() => handleKnowledgeConfig()}>{t('application.globalConfig')}</Button>
|
||||||
}
|
}
|
||||||
>
|
>
|
||||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-[12px]">
|
<div className="rb:flex rb:items-center rb:justify-between rb:mb-3">
|
||||||
<div className="rb:font-medium rb:leading-[20px]">{t('application.associatedKnowledgeBase')}</div>
|
<div className="rb:font-medium rb:leading-5">{t('application.associatedKnowledgeBase')}</div>
|
||||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddKnowledge}>+{t('application.addKnowledgeBase')}</Button>
|
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddKnowledge}>+{t('application.addKnowledgeBase')}</Button>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
@@ -115,21 +112,21 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
|||||||
dataSource={knowledgeList}
|
dataSource={knowledgeList}
|
||||||
renderItem={(item) => (
|
renderItem={(item) => (
|
||||||
<List.Item>
|
<List.Item>
|
||||||
<div key={item.id} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-[8px]">
|
<div key={item.id} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
||||||
<div className="rb:font-medium rb:leading-[16px]">
|
<div className="rb:font-medium rb:leading-4">
|
||||||
{item.name}
|
{item.name}
|
||||||
<Tag color={item.status === 1 ? 'success' : item.status === 0 ? 'default' : 'error'} className="rb:ml-[8px]">
|
<Tag color={item.status === 1 ? 'success' : item.status === 0 ? 'default' : 'error'} className="rb:ml-2">
|
||||||
{item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')}
|
{item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')}
|
||||||
</Tag>
|
</Tag>
|
||||||
<div className="rb:mt-[4px] rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[20px]">{t('application.contains', {include_count: item.doc_num})}</div>
|
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-5">{t('application.contains', {include_count: item.doc_num})}</div>
|
||||||
</div>
|
</div>
|
||||||
<Space size={12}>
|
<Space size={12}>
|
||||||
<div
|
<div
|
||||||
className="rb:w-[24px] rb:h-[24px] rb:cursor-pointer rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
||||||
onClick={() => handleEditKnowledge(item)}
|
onClick={() => handleEditKnowledge(item)}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className="rb:w-[24px] rb:h-[24px] rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||||
onClick={() => handleDeleteKnowledge(item.id)}
|
onClick={() => handleDeleteKnowledge(item.id)}
|
||||||
></div>
|
></div>
|
||||||
</Space>
|
</Space>
|
||||||
|
|||||||
149
web/src/views/ApplicationConfig/components/ToolList.tsx
Normal file
149
web/src/views/ApplicationConfig/components/ToolList.tsx
Normal file
@@ -0,0 +1,149 @@
|
|||||||
|
import { type FC, useRef, useState, useEffect } from 'react'
|
||||||
|
import { useTranslation } from 'react-i18next'
|
||||||
|
import { Space, Button, List, Switch } from 'antd'
|
||||||
|
import Card from './Card'
|
||||||
|
import type {
|
||||||
|
ToolModalRef,
|
||||||
|
ToolOption
|
||||||
|
} from '../types'
|
||||||
|
import Empty from '@/components/Empty'
|
||||||
|
import ToolModal from './ToolModal'
|
||||||
|
import { getToolMethods, getToolDetail } from '@/api/tools'
|
||||||
|
|
||||||
|
const ToolList: FC<{ data: ToolOption[]; onUpdate: (config: ToolOption[]) => void}> = ({data, onUpdate}) => {
|
||||||
|
const { t } = useTranslation()
|
||||||
|
const toolModalRef = useRef<ToolModalRef>(null)
|
||||||
|
const [toolList, setToolList] = useState<ToolOption[]>([])
|
||||||
|
useEffect(() => {
|
||||||
|
if (data) {
|
||||||
|
const processedData = data.map(async (item) => {
|
||||||
|
if (!item.label && item.tool_id) {
|
||||||
|
try {
|
||||||
|
const [toolDetail, methods] = await Promise.all([
|
||||||
|
getToolDetail(item.tool_id),
|
||||||
|
getToolMethods(item.tool_id)
|
||||||
|
])
|
||||||
|
|
||||||
|
switch ((toolDetail as any).tool_type) {
|
||||||
|
case 'mcp':
|
||||||
|
const mcpFilterItem = (methods as any[]).find(vo => vo.name === item.operation)
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
label: mcpFilterItem?.description,
|
||||||
|
method_id: mcpFilterItem?.method_id,
|
||||||
|
value: mcpFilterItem?.name,
|
||||||
|
description: mcpFilterItem?.description,
|
||||||
|
parameters: mcpFilterItem?.parameters
|
||||||
|
}
|
||||||
|
break
|
||||||
|
case 'builtin':
|
||||||
|
if ((methods as any[]).length > 1) {
|
||||||
|
const builtinFilterItem = (methods as any[]).find(vo => vo.name === item.operation)
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
label: builtinFilterItem?.description,
|
||||||
|
method_id: builtinFilterItem?.method_id,
|
||||||
|
value: builtinFilterItem?.name,
|
||||||
|
description: builtinFilterItem?.description,
|
||||||
|
parameters: builtinFilterItem?.parameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
label: (methods as any[])[0]?.description,
|
||||||
|
method_id: (methods as any[])[0]?.method_id,
|
||||||
|
value: (methods as any[])[0]?.name,
|
||||||
|
description: (methods as any[])[0]?.description,
|
||||||
|
parameters: (methods as any[])[0]?.parameters
|
||||||
|
}
|
||||||
|
break
|
||||||
|
default:
|
||||||
|
const customFilterItem = (methods as any[]).find(vo => vo.method_id === item.operation)
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
label: customFilterItem?.name,
|
||||||
|
method_id: customFilterItem?.method_id,
|
||||||
|
value: customFilterItem?.name,
|
||||||
|
description: customFilterItem?.description,
|
||||||
|
parameters: customFilterItem?.parameters
|
||||||
|
}
|
||||||
|
}
|
||||||
|
} catch (error) {
|
||||||
|
return item
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
|
||||||
|
Promise.all(processedData).then(setToolList)
|
||||||
|
}
|
||||||
|
}, [data])
|
||||||
|
|
||||||
|
console.log('toolList', toolList)
|
||||||
|
|
||||||
|
const handleAddTool = () => {
|
||||||
|
toolModalRef.current?.handleOpen()
|
||||||
|
}
|
||||||
|
const updateTools = (tool: ToolOption) => {
|
||||||
|
const list = [...toolList, tool]
|
||||||
|
setToolList(list)
|
||||||
|
onUpdate(list)
|
||||||
|
}
|
||||||
|
const handleDeleteTool = (index: number) => {
|
||||||
|
const list = toolList.filter((_item, idx) => idx !== index)
|
||||||
|
setToolList([...list])
|
||||||
|
onUpdate(list)
|
||||||
|
}
|
||||||
|
const handleChangeEnabled = (index: number) => {
|
||||||
|
const list = toolList.map((item, idx) => {
|
||||||
|
if (idx === index) {
|
||||||
|
return {
|
||||||
|
...item,
|
||||||
|
enabled: !item.enabled
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return item
|
||||||
|
})
|
||||||
|
setToolList([...list])
|
||||||
|
onUpdate(list)
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<Card
|
||||||
|
title={t('application.toolConfiguration')}
|
||||||
|
extra={
|
||||||
|
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddTool}>+{t('application.addTool')}</Button>
|
||||||
|
}
|
||||||
|
>
|
||||||
|
|
||||||
|
{toolList.length === 0
|
||||||
|
? <Empty size={88} />
|
||||||
|
:
|
||||||
|
<List
|
||||||
|
grid={{ gutter: 12, column: 1 }}
|
||||||
|
dataSource={toolList}
|
||||||
|
renderItem={(item, index) => (
|
||||||
|
<List.Item>
|
||||||
|
<div key={index} className="rb:flex rb:items-center rb:justify-between rb:p-[12px_16px] rb:bg-[#FBFDFF] rb:border rb:border-[#DFE4ED] rb:rounded-lg">
|
||||||
|
<div className="rb:font-medium rb:leading-4">
|
||||||
|
{item.label}
|
||||||
|
</div>
|
||||||
|
<Space size={12}>
|
||||||
|
<div
|
||||||
|
className="rb:w-6 rb:h-6 rb:cursor-pointer rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||||
|
onClick={() => handleDeleteTool(index)}
|
||||||
|
></div>
|
||||||
|
<Switch checked={item.enabled} onChange={() => handleChangeEnabled(index)} />
|
||||||
|
</Space>
|
||||||
|
</div>
|
||||||
|
</List.Item>
|
||||||
|
)}
|
||||||
|
/>
|
||||||
|
}
|
||||||
|
<ToolModal
|
||||||
|
ref={toolModalRef}
|
||||||
|
refresh={updateTools}
|
||||||
|
/>
|
||||||
|
</Card>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
export default ToolList
|
||||||
145
web/src/views/ApplicationConfig/components/ToolModal.tsx
Normal file
145
web/src/views/ApplicationConfig/components/ToolModal.tsx
Normal file
@@ -0,0 +1,145 @@
|
|||||||
|
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||||
|
import { Form, Cascader, type CascaderProps } from 'antd';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import type { ToolModalRef, ToolOption } from '../types'
|
||||||
|
import RbModal from '@/components/RbModal'
|
||||||
|
import { getToolMethods, getTools } from '@/api/tools'
|
||||||
|
import type { ToolType, ToolItem } from '@/views/ToolManagement/types'
|
||||||
|
|
||||||
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
|
interface ToolModalProps {
|
||||||
|
refresh: (tool: ToolOption) => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ToolModal = forwardRef<ToolModalRef, ToolModalProps>(({
|
||||||
|
refresh,
|
||||||
|
}, ref) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [visible, setVisible] = useState(false);
|
||||||
|
const [form] = Form.useForm();
|
||||||
|
const [loading, setLoading] = useState(false)
|
||||||
|
const [optionList, setOptionList] = useState<ToolOption[]>([
|
||||||
|
{ value: 'mcp', label: t('tool.mcp'), isLeaf: false },
|
||||||
|
{ value: 'builtin', label: t('tool.inner'), isLeaf: false },
|
||||||
|
{ value: 'custom', label: t('tool.custom'), isLeaf: false },
|
||||||
|
])
|
||||||
|
const [selectdTools, setSelectedTools] = useState<ToolOption[]>([])
|
||||||
|
|
||||||
|
// 封装取消方法,添加关闭弹窗逻辑
|
||||||
|
const handleClose = () => {
|
||||||
|
setVisible(false);
|
||||||
|
form.resetFields();
|
||||||
|
setLoading(false)
|
||||||
|
setSelectedTools([])
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleOpen = () => {
|
||||||
|
setVisible(true);
|
||||||
|
form.resetFields();
|
||||||
|
setSelectedTools([])
|
||||||
|
};
|
||||||
|
// 封装保存方法,添加提交逻辑
|
||||||
|
const handleSave = () => {
|
||||||
|
form.validateFields().then(() => {
|
||||||
|
setLoading(false)
|
||||||
|
let operation: any = undefined
|
||||||
|
if (selectdTools[0].value === 'mcp' || (selectdTools[0].value === 'builtin' && selectdTools[1]?.children && selectdTools[1].children.length > 1)) {
|
||||||
|
operation = selectdTools[2].value
|
||||||
|
} else if (selectdTools[0].value === 'custom') {
|
||||||
|
operation = selectdTools[2].method_id
|
||||||
|
}
|
||||||
|
|
||||||
|
const tool = {
|
||||||
|
...selectdTools[2],
|
||||||
|
label: selectdTools[0].value === 'custom' ? selectdTools[2].label : selectdTools[2].description,
|
||||||
|
tool_id: selectdTools[1].value as string,
|
||||||
|
enabled: true
|
||||||
|
}
|
||||||
|
if (operation) {
|
||||||
|
tool.operation = operation
|
||||||
|
}
|
||||||
|
refresh(tool)
|
||||||
|
handleClose()
|
||||||
|
})
|
||||||
|
}
|
||||||
|
const loadData = (selectedOptions: ToolOption[]) => {
|
||||||
|
const targetOption = selectedOptions[selectedOptions.length - 1];
|
||||||
|
if (selectedOptions.length === 1) {
|
||||||
|
getTools({ tool_type: targetOption.value as ToolType })
|
||||||
|
.then(res => {
|
||||||
|
const response = res as ToolItem[]
|
||||||
|
targetOption.children = response.map((vo: any) => {
|
||||||
|
return {
|
||||||
|
value: vo.id,
|
||||||
|
label: vo.name,
|
||||||
|
isLeaf: response.length === 0,
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setOptionList([...optionList])
|
||||||
|
})
|
||||||
|
} else {
|
||||||
|
getToolMethods(targetOption.value as string)
|
||||||
|
.then(res => {
|
||||||
|
const response = res as Array<{ method_id: string; name: string }>
|
||||||
|
targetOption.children = response.map((vo: any) => {
|
||||||
|
return {
|
||||||
|
value: vo.name,
|
||||||
|
label: vo.name,
|
||||||
|
description: vo.description,
|
||||||
|
isLeaf: true,
|
||||||
|
method_id: vo.method_id,
|
||||||
|
parameters: vo.parameters
|
||||||
|
}
|
||||||
|
})
|
||||||
|
setOptionList([...optionList])
|
||||||
|
})
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const handleChange: CascaderProps<ToolOption>['onChange'] = (_value, selectedOptions) => {
|
||||||
|
console.log('selectedOptions', selectedOptions)
|
||||||
|
setSelectedTools(selectedOptions)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 暴露给父组件的方法
|
||||||
|
useImperativeHandle(ref, () => ({
|
||||||
|
handleOpen,
|
||||||
|
handleClose
|
||||||
|
}));
|
||||||
|
|
||||||
|
return (
|
||||||
|
<RbModal
|
||||||
|
title={t(`application.addTool`)}
|
||||||
|
open={visible}
|
||||||
|
onCancel={handleClose}
|
||||||
|
okText={t('common.save')}
|
||||||
|
onOk={handleSave}
|
||||||
|
confirmLoading={loading}
|
||||||
|
>
|
||||||
|
<Form
|
||||||
|
form={form}
|
||||||
|
layout="vertical"
|
||||||
|
>
|
||||||
|
<FormItem
|
||||||
|
name="agent_id"
|
||||||
|
label={t('application.tool')}
|
||||||
|
rules={[
|
||||||
|
{ required: true, message: t('common.pleaseSelect') },
|
||||||
|
]}
|
||||||
|
>
|
||||||
|
<Cascader
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
options={optionList}
|
||||||
|
loadData={loadData}
|
||||||
|
onChange={handleChange}
|
||||||
|
changeOnSelect={false}
|
||||||
|
/>
|
||||||
|
</FormItem>
|
||||||
|
</Form>
|
||||||
|
</RbModal>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export default ToolModal;
|
||||||
@@ -78,14 +78,7 @@ export interface Config extends MultiAgentConfig {
|
|||||||
knowledge_retrieval: KnowledgeConfig | null;
|
knowledge_retrieval: KnowledgeConfig | null;
|
||||||
memory?: MemoryConfig;
|
memory?: MemoryConfig;
|
||||||
variables: Variable[];
|
variables: Variable[];
|
||||||
tools: {
|
tools: ToolOption[];
|
||||||
web_search: {
|
|
||||||
enabled: boolean;
|
|
||||||
config: {
|
|
||||||
web_search: boolean;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
is_active: boolean;
|
is_active: boolean;
|
||||||
created_at: number;
|
created_at: number;
|
||||||
updated_at: number;
|
updated_at: number;
|
||||||
@@ -211,4 +204,31 @@ export interface AiPromptForm {
|
|||||||
model_id?: string;
|
model_id?: string;
|
||||||
message?: string;
|
message?: string;
|
||||||
current_prompt?: string;
|
current_prompt?: string;
|
||||||
|
}
|
||||||
|
export interface ToolModalRef {
|
||||||
|
handleOpen: () => void;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface ToolOption {
|
||||||
|
value?: string | number | null;
|
||||||
|
label?: React.ReactNode;
|
||||||
|
description?: string;
|
||||||
|
children?: ToolOption[];
|
||||||
|
isLeaf?: boolean;
|
||||||
|
method_id?: string;
|
||||||
|
operation?: string;
|
||||||
|
parameters?: Parameter[];
|
||||||
|
tool_id?: string;
|
||||||
|
enabled?: boolean;
|
||||||
|
}
|
||||||
|
export interface Parameter {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
description: string;
|
||||||
|
required: boolean;
|
||||||
|
default: any;
|
||||||
|
enum: null | string[];
|
||||||
|
minimum: number;
|
||||||
|
maximum: number;
|
||||||
|
pattern: null | string;
|
||||||
}
|
}
|
||||||
Reference in New Issue
Block a user