diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx
index c6aa63e8..f3e327ec 100644
--- a/web/src/views/ApplicationConfig/Agent.tsx
+++ b/web/src/views/ApplicationConfig/Agent.tsx
@@ -18,7 +18,9 @@ import type {
Variable,
MemoryConfig,
AiPromptModalRef,
- Source
+ Source,
+ ToolModalRef,
+ ToolOption
} from './types'
import type { Model } from '@/views/ModelManagement/types'
import { getModelList } from '@/api/models';
@@ -31,6 +33,8 @@ import { memoryConfigListUrl } from '@/api/memory'
import CustomSelect from '@/components/CustomSelect'
import aiPrompt from '@/assets/images/application/aiPrompt.png'
import AiPromptModal from './components/AiPromptModal'
+import ToolModal from './components/ToolModal'
+import ToolList from './components/ToolList'
const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => {
return (
@@ -47,12 +51,12 @@ const LabelWrapper: FC<{title: string, className?: string; children?: ReactNode}
)
}
-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();
return (
-
-
+
+ {desc && }
((_props, ref) => {
const [formData, setFormData] = useState<{
default_model_config_id?: string,
model_parameters?: Config['model_parameters'],
+ tools: ToolOption[],
} | null>(null)
const values = Form.useWatch<{
memoryEnabled: boolean;
memory_content?: string | number;
- webSearch: boolean;
} & Config>([], form)
const [knowledgeConfig, setKnowledgeConfig] = useState({ knowledge_bases: [] })
@@ -149,17 +153,21 @@ const Agent = forwardRef((_props, ref) => {
setLoading(true)
getApplicationConfig(id as string).then(res => {
const response = res as Config
- setData(response)
+ setData({
+ ...response,
+ tools: Array.isArray(response.tools) ? response.tools : []
+ })
const { memory, tools } = response
form.setFieldsValue({
...response,
memoryEnabled: memory?.enabled || false,
memory_content: memory?.memory_content ? Number(memory?.memory_content) : undefined,
- webSearch: tools?.web_search?.enabled || false,
+ tools: Array.isArray(tools) ? tools : []
})
setFormData({
default_model_config_id: response.default_model_config_id,
model_parameters: response.model_parameters || {},
+ tools: Array.isArray(tools) ? tools : []
})
if (response?.knowledge_retrieval?.knowledge_bases?.length) {
getDefaultKnowledgeList(response)
@@ -260,8 +268,9 @@ const Agent = forwardRef((_props, ref) => {
// 保存Agent配置
const handleSave = (flag = true) => {
if (!isSave || !data) return Promise.resolve()
- const { memoryEnabled, memory_content, webSearch, ...rest } = values
+ const { memoryEnabled, memory_content, ...rest } = values
const { knowledge_bases = [], ...knowledgeRest } = knowledgeConfig || {}
+
// 从原数据中获取memory的其他必要属性
const originalMemory = data.memory || ({} as MemoryConfig)
@@ -285,15 +294,10 @@ const Agent = forwardRef((_props, ref) => {
...(item.config || {})
}))
} as KnowledgeConfig : null,
- tools: {
- web_search: {
- enabled: webSearch,
- config: {
- web_search: webSearch
- }
- }
- }
+ tools: toolList
}
+
+ console.log('params', rest, params)
return new Promise((resolve, reject) => {
saveAgentConfig(data.app_id, params)
@@ -342,6 +346,19 @@ const Agent = forwardRef((_props, ref) => {
const updatePrompt = (value: string) => {
form.setFieldValue('system_prompt', value)
}
+
+ const toolModalRef = useRef(null)
+ const [toolList, setToolList] = useState([])
+ const handleAddTool = () => {
+ toolModalRef.current?.handleOpen()
+ }
+ const updateTools = (tool: ToolOption) => {
+ const tools = [...toolList, tool]
+ setToolList(tools)
+ form.setFieldValue('tools', tools)
+ }
+
+ console.log('toolList', toolList)
return (
<>
{loading && }
@@ -410,14 +427,12 @@ const Agent = forwardRef((_props, ref) => {
data={data?.variables}
onUpdate={setVariableList}
/>
+
{/* 工具配置 */}
-
-
-
- {/*
- */}
-
-
+
@@ -454,6 +469,10 @@ const Agent = forwardRef((_props, ref) => {
defaultModel={defaultModel}
refresh={updatePrompt}
/>
+
>
);
});
diff --git a/web/src/views/ApplicationConfig/components/Knowledge.tsx b/web/src/views/ApplicationConfig/components/Knowledge.tsx
index 0fac117d..bc1207e4 100644
--- a/web/src/views/ApplicationConfig/components/Knowledge.tsx
+++ b/web/src/views/ApplicationConfig/components/Knowledge.tsx
@@ -31,10 +31,6 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
setEditConfig({ ...(data || {}) })
const knowledge_bases = [...(data.knowledge_bases || [])]
setKnowledgeList(knowledge_bases)
- onUpdate(prev => ({
- ...prev,
- knowledge_bases: knowledge_bases,
- }))
}
}, [data])
@@ -47,10 +43,10 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
const handleDeleteKnowledge = (id: string) => {
const list = knowledgeList.filter(item => item.id !== id)
setKnowledgeList([...list])
- onUpdate(prev => ({
- ...prev,
+ onUpdate({
+ ...editConfig,
knowledge_bases: [...list],
- }))
+ })
}
const handleEditKnowledge = (item: KnowledgeBase) => {
knowledgeConfigModalRef.current?.handleOpen(item)
@@ -69,10 +65,10 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
list = [...values as KnowledgeBase[]]
}
setKnowledgeList([...list])
- onUpdate(prev => ({
- ...prev,
+ onUpdate({
+ ...editConfig,
knowledge_bases: [...list],
- }))
+ })
} else if (type === 'knowledgeConfig') {
const index = knowledgeList.findIndex(item => item.id === (values as KnowledgeBase).kb_id)
const list = [...knowledgeList]
@@ -81,18 +77,19 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
config: {...values as KnowledgeConfigForm}
}
setKnowledgeList([...list])
- onUpdate(prev => ({
- ...prev,
+ onUpdate({
+ ...editConfig,
knowledge_bases: [...list],
- }))
+ })
} else if (type === 'rerankerConfig') {
- setEditConfig(prev => ({ ...prev, ...(values as RerankerConfig) }))
- onUpdate(prev => ({
- ...prev,
- ...values,
- reranker_id: values.rerank_model ? values.reranker_id : undefined,
- reranker_top_k: values.rerank_model ? values.reranker_top_k : undefined,
- }))
+ const rerankerValues = values as RerankerConfig
+ setEditConfig(prev => ({ ...prev, ...rerankerValues }))
+ onUpdate({
+ ...editConfig,
+ ...rerankerValues,
+ reranker_id: rerankerValues.rerank_model ? rerankerValues.reranker_id : undefined,
+ reranker_top_k: rerankerValues.rerank_model ? rerankerValues.reranker_top_k : undefined,
+ })
}
}
return (
@@ -102,8 +99,8 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
}
>
-
-
{t('application.associatedKnowledgeBase')}
+
+
{t('application.associatedKnowledgeBase')}
@@ -115,21 +112,21 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
dataSource={knowledgeList}
renderItem={(item) => (
-
-
+
+
{item.name}
-
+
{item.status === 1 ? t('common.enable') : item.status === 0 ? t('common.disabled') : t('common.deleted')}
- {t('application.contains', {include_count: item.doc_num})}
+ {t('application.contains', {include_count: item.doc_num})}
handleEditKnowledge(item)}
>
handleDeleteKnowledge(item.id)}
>
diff --git a/web/src/views/ApplicationConfig/components/ToolList.tsx b/web/src/views/ApplicationConfig/components/ToolList.tsx
new file mode 100644
index 00000000..9834b186
--- /dev/null
+++ b/web/src/views/ApplicationConfig/components/ToolList.tsx
@@ -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
(null)
+ const [toolList, setToolList] = useState([])
+ 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 (
+ +{t('application.addTool')}
+ }
+ >
+
+ {toolList.length === 0
+ ?
+ :
+ (
+
+
+
+ {item.label}
+
+
+ handleDeleteTool(index)}
+ >
+ handleChangeEnabled(index)} />
+
+
+
+ )}
+ />
+ }
+
+
+ )
+}
+export default ToolList
\ No newline at end of file
diff --git a/web/src/views/ApplicationConfig/components/ToolModal.tsx b/web/src/views/ApplicationConfig/components/ToolModal.tsx
new file mode 100644
index 00000000..64fd6044
--- /dev/null
+++ b/web/src/views/ApplicationConfig/components/ToolModal.tsx
@@ -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(({
+ refresh,
+}, ref) => {
+ const { t } = useTranslation();
+ const [visible, setVisible] = useState(false);
+ const [form] = Form.useForm();
+ const [loading, setLoading] = useState(false)
+ const [optionList, setOptionList] = useState([
+ { 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([])
+
+ // 封装取消方法,添加关闭弹窗逻辑
+ 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['onChange'] = (_value, selectedOptions) => {
+ console.log('selectedOptions', selectedOptions)
+ setSelectedTools(selectedOptions)
+ }
+
+ // 暴露给父组件的方法
+ useImperativeHandle(ref, () => ({
+ handleOpen,
+ handleClose
+ }));
+
+ return (
+
+
+
+ );
+});
+
+export default ToolModal;
\ No newline at end of file
diff --git a/web/src/views/ApplicationConfig/types.ts b/web/src/views/ApplicationConfig/types.ts
index 3a1c262c..cc6852b5 100644
--- a/web/src/views/ApplicationConfig/types.ts
+++ b/web/src/views/ApplicationConfig/types.ts
@@ -78,14 +78,7 @@ export interface Config extends MultiAgentConfig {
knowledge_retrieval: KnowledgeConfig | null;
memory?: MemoryConfig;
variables: Variable[];
- tools: {
- web_search: {
- enabled: boolean;
- config: {
- web_search: boolean;
- }
- }
- };
+ tools: ToolOption[];
is_active: boolean;
created_at: number;
updated_at: number;
@@ -211,4 +204,31 @@ export interface AiPromptForm {
model_id?: string;
message?: 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;
}
\ No newline at end of file