Merge pull request #154 from SuanmoSuanyangTechnology/feature/agent_zy
Feature/agent zy
This commit is contained in:
@@ -658,8 +658,8 @@ export const zh = {
|
||||
priority: '结构化整合',
|
||||
addTool: '添加工具',
|
||||
tool: '工具',
|
||||
variableConfig: '配置变量'
|
||||
},
|
||||
// 角色管理相关翻译
|
||||
role: {
|
||||
roleManagement: '角色管理',
|
||||
roleId: '角色ID',
|
||||
|
||||
@@ -332,21 +332,6 @@
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"parent": 0,
|
||||
"code": "member",
|
||||
"label": "成员管理",
|
||||
"i18nKey": "menu.memberManagement",
|
||||
"path": "/member",
|
||||
"enable": true,
|
||||
"display": true,
|
||||
"level": 1,
|
||||
"sort": 0,
|
||||
"icon": null,
|
||||
"iconActive": null,
|
||||
"subs": null
|
||||
},
|
||||
{
|
||||
"id": 10,
|
||||
"parent": 0,
|
||||
@@ -377,6 +362,21 @@
|
||||
"iconActive": null,
|
||||
"subs": null
|
||||
},
|
||||
{
|
||||
"id": 19,
|
||||
"parent": 0,
|
||||
"code": "member",
|
||||
"label": "成员管理",
|
||||
"i18nKey": "menu.memberManagement",
|
||||
"path": "/member",
|
||||
"enable": true,
|
||||
"display": true,
|
||||
"level": 1,
|
||||
"sort": 0,
|
||||
"icon": null,
|
||||
"iconActive": null,
|
||||
"subs": null
|
||||
},
|
||||
{
|
||||
"id": 12,
|
||||
"parent": 0,
|
||||
|
||||
@@ -13,26 +13,25 @@ import type {
|
||||
Config,
|
||||
ModelConfig,
|
||||
AgentRef,
|
||||
KnowledgeBase,
|
||||
KnowledgeConfig,
|
||||
Variable,
|
||||
MemoryConfig,
|
||||
AiPromptModalRef,
|
||||
Source,
|
||||
ToolOption
|
||||
ChatVariableConfigModalRef
|
||||
} from './types'
|
||||
import type { Variable } from './components/VariableList/types'
|
||||
import type { KnowledgeConfig } from './components/Knowledge/types'
|
||||
import type { Model } from '@/views/ModelManagement/types'
|
||||
import { getModelList } from '@/api/models';
|
||||
import { saveAgentConfig } from '@/api/application'
|
||||
import Knowledge from './components/Knowledge'
|
||||
import VariableList from './components/VariableList'
|
||||
import Knowledge from './components/Knowledge/Knowledge'
|
||||
import VariableList from './components/VariableList/VariableList'
|
||||
import { getApplicationConfig } from '@/api/application'
|
||||
import { getKnowledgeBaseList } from '@/api/knowledgeBase'
|
||||
import { memoryConfigListUrl } from '@/api/memory'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import aiPrompt from '@/assets/images/application/aiPrompt.png'
|
||||
import AiPromptModal from './components/AiPromptModal'
|
||||
import ToolList from './components/ToolList'
|
||||
import ToolList from './components/ToolList/ToolList'
|
||||
import ChatVariableConfigModal from './components/ChatVariableConfigModal';
|
||||
|
||||
const DescWrapper: FC<{desc: string, className?: string}> = ({desc, className}) => {
|
||||
return (
|
||||
@@ -66,7 +65,7 @@ const SwitchWrapper: FC<{ title: string, desc?: string, name: string | string[];
|
||||
</div>
|
||||
)
|
||||
}
|
||||
const SelectWrapper: FC<{ title: string, desc: string, name: string, url: string }> = ({ title, desc, name, url }) => {
|
||||
const SelectWrapper: FC<{ title: string, desc: string, name: string | string[], url: string }> = ({ title, desc, name, url }) => {
|
||||
const { t } = useTranslation();
|
||||
return (
|
||||
<>
|
||||
@@ -77,6 +76,7 @@ const SelectWrapper: FC<{ title: string, desc: string, name: string, url: string
|
||||
className="rb:mb-0!"
|
||||
>
|
||||
<CustomSelect
|
||||
placeholder={t('common.pleaseSelect')}
|
||||
url={url}
|
||||
hasAll={false}
|
||||
valueKey='config_id'
|
||||
@@ -99,54 +99,22 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
const [modelList, setModelList] = useState<Model[]>([])
|
||||
const [defaultModel, setDefaultModel] = useState<Model | null>(null)
|
||||
const [chatList, setChatList] = useState<ChatData[]>([])
|
||||
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;
|
||||
} & Config>([], form)
|
||||
|
||||
const [knowledgeConfig, setKnowledgeConfig] = useState<KnowledgeConfig>({ knowledge_bases: [] })
|
||||
const [variableList, setVariableList] = useState<Variable[]>([])
|
||||
const values = Form.useWatch<Config>([], form)
|
||||
const [isSave, setIsSave] = useState(false)
|
||||
const initialized = useRef(false)
|
||||
const [toolList, setToolList] = useState<ToolOption[]>([])
|
||||
|
||||
// 初始化完成标记
|
||||
useEffect(() => {
|
||||
if (data && values && formData) {
|
||||
if (data) {
|
||||
initialized.current = true
|
||||
}
|
||||
}, [data, values, formData])
|
||||
}, [data])
|
||||
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [knowledgeConfig])
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [variableList])
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [formData])
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [values])
|
||||
useEffect(() => {
|
||||
if (!initialized.current) return
|
||||
if (isSave) return
|
||||
setIsSave(true)
|
||||
}, [toolList])
|
||||
|
||||
useEffect(() => {
|
||||
getModels()
|
||||
@@ -157,68 +125,19 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
setLoading(true)
|
||||
getApplicationConfig(id as string).then(res => {
|
||||
const response = res as Config
|
||||
setData({
|
||||
...response,
|
||||
tools: Array.isArray(response.tools) ? response.tools : []
|
||||
})
|
||||
const { memory, tools } = response
|
||||
let allTools = Array.isArray(response.tools) ? response.tools : []
|
||||
form.setFieldsValue({
|
||||
...response,
|
||||
memoryEnabled: memory?.enabled || false,
|
||||
memory_content: memory?.memory_content ? Number(memory?.memory_content) : undefined,
|
||||
tools: Array.isArray(tools) ? tools : []
|
||||
tools: allTools
|
||||
})
|
||||
setFormData({
|
||||
default_model_config_id: response.default_model_config_id,
|
||||
model_parameters: response.model_parameters || {},
|
||||
tools: Array.isArray(tools) ? tools : []
|
||||
setData({
|
||||
...response,
|
||||
tools: allTools
|
||||
})
|
||||
if (response?.knowledge_retrieval?.knowledge_bases?.length) {
|
||||
getDefaultKnowledgeList(response)
|
||||
}
|
||||
if (response?.tools?.length) {
|
||||
setToolList(response?.tools)
|
||||
}
|
||||
}).finally(() => {
|
||||
setLoading(false)
|
||||
})
|
||||
}
|
||||
const getDefaultKnowledgeList = (data: Config) => {
|
||||
if (!data || !data.knowledge_retrieval || !data.knowledge_retrieval?.knowledge_bases?.length) {
|
||||
return
|
||||
}
|
||||
const initialList = [...(data?.knowledge_retrieval?.knowledge_bases || [])]
|
||||
getKnowledgeBaseList(undefined, {
|
||||
kb_ids: initialList.map(vo => vo.kb_id).join(','),
|
||||
page: 1,
|
||||
pagesize: 100,
|
||||
})
|
||||
.then(res => {
|
||||
const list = res.items || []
|
||||
const knowledge_bases: KnowledgeBase[] = list.map(item => {
|
||||
const filterItem = initialList.find(vo => vo.kb_id === item.id)
|
||||
return {
|
||||
...item,
|
||||
...filterItem
|
||||
}
|
||||
})
|
||||
setKnowledgeConfig(prev => ({
|
||||
...prev,
|
||||
knowledge_bases: [...knowledge_bases]
|
||||
}))
|
||||
setData((prev) => {
|
||||
prev = prev as Config
|
||||
const knowledge_retrieval: KnowledgeConfig = {
|
||||
...(prev?.knowledge_retrieval || {}),
|
||||
knowledge_bases: [...knowledge_bases]
|
||||
}
|
||||
return {
|
||||
...(prev || {}),
|
||||
knowledge_retrieval
|
||||
}
|
||||
})
|
||||
})
|
||||
}
|
||||
|
||||
const refresh = (vo: ModelConfig, type: Source) => {
|
||||
if (type === 'model') {
|
||||
@@ -227,15 +146,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
default_model_config_id,
|
||||
model_parameters: {...rest}
|
||||
})
|
||||
setFormData((prevState) => {
|
||||
const prev = prevState as Config
|
||||
return {
|
||||
...(prev || {}),
|
||||
default_model_config_id,
|
||||
model_parameters: {...rest}
|
||||
};
|
||||
})
|
||||
if (default_model_config_id === formData?.default_model_config_id) {
|
||||
if (default_model_config_id === values?.default_model_config_id) {
|
||||
setChatList([{
|
||||
label: vo.label || '',
|
||||
model_config_id: default_model_config_id || '',
|
||||
@@ -279,24 +190,20 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
// 保存Agent配置
|
||||
const handleSave = (flag = true) => {
|
||||
if (!isSave || !data) return Promise.resolve()
|
||||
const { memoryEnabled, memory_content, ...rest } = values
|
||||
const { knowledge_bases = [], ...knowledgeRest } = knowledgeConfig || {}
|
||||
|
||||
|
||||
const { memory, knowledge_retrieval, tools, ...rest } = values
|
||||
const { knowledge_bases = [], ...knowledgeRest } = knowledge_retrieval || {}
|
||||
const { memory_content } = memory || {}
|
||||
// 从原数据中获取memory的其他必要属性
|
||||
const originalMemory = data.memory || ({} as MemoryConfig)
|
||||
|
||||
const params: Config = {
|
||||
...data,
|
||||
...rest,
|
||||
...(formData || {}),
|
||||
memory: {
|
||||
...originalMemory,
|
||||
enabled: memoryEnabled,
|
||||
...memory,
|
||||
memory_content: memory_content ? String(memory_content) : '',
|
||||
max_history: originalMemory.max_history || '',
|
||||
},
|
||||
variables: variableList || [],
|
||||
knowledge_retrieval: knowledge_bases.length > 0 ? {
|
||||
...data.knowledge_retrieval,
|
||||
...knowledgeRest,
|
||||
@@ -305,14 +212,12 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
...(item.config || {})
|
||||
}))
|
||||
} as KnowledgeConfig : null,
|
||||
tools: toolList.map(vo => ({
|
||||
tools: tools.map(vo => ({
|
||||
tool_id: vo.tool_id,
|
||||
operation: vo.operation,
|
||||
enabled: vo.enabled
|
||||
}))
|
||||
}
|
||||
|
||||
console.log('params', rest, params)
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
saveAgentConfig(data.app_id, params)
|
||||
@@ -338,8 +243,8 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
modelConfigModalRef.current?.handleOpen('chat')
|
||||
}
|
||||
useEffect(() => {
|
||||
if (formData?.default_model_config_id && modelList.length > 0) {
|
||||
const filterValue = modelList.find(item => item.id === formData.default_model_config_id)
|
||||
if (values?.default_model_config_id && modelList.length > 0) {
|
||||
const filterValue = modelList.find(item => item.id === values.default_model_config_id)
|
||||
setDefaultModel(filterValue as Model | null)
|
||||
setChatList([{
|
||||
label: filterValue?.name || '',
|
||||
@@ -348,7 +253,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
list: []
|
||||
}])
|
||||
}
|
||||
}, [modelList, formData?.default_model_config_id])
|
||||
}, [modelList, values?.default_model_config_id])
|
||||
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleSave
|
||||
@@ -360,8 +265,31 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
}
|
||||
const updatePrompt = (value: string) => {
|
||||
form.setFieldValue('system_prompt', value)
|
||||
const variables = value.match(/\{\{([^}]+)\}\}/g)?.map(match => match.slice(2, -2)) || []
|
||||
const uniqueVariables = [...new Set(variables)]
|
||||
const newVariableList: Variable[] = uniqueVariables.map((name, index) => ({
|
||||
index,
|
||||
type: 'text',
|
||||
name,
|
||||
display_name: name,
|
||||
required: false
|
||||
}))
|
||||
updateVariableList(newVariableList)
|
||||
}
|
||||
|
||||
const updateVariableList = (list: Variable[]) => {
|
||||
form.setFieldValue('variables', [...list])
|
||||
setChatVariables([...list])
|
||||
}
|
||||
const chatVariableConfigModalRef = useRef<ChatVariableConfigModalRef>(null)
|
||||
const [chatVariables, setChatVariables] = useState<Variable[]>([])
|
||||
const handleOpenVariableConfig = () => {
|
||||
chatVariableConfigModalRef.current?.handleOpen(chatVariables)
|
||||
}
|
||||
const handleSaveChatVariable = (values: Variable[]) => {
|
||||
setChatVariables(values)
|
||||
}
|
||||
console.log('values', values)
|
||||
return (
|
||||
<>
|
||||
{loading && <Spin fullscreen></Spin>}
|
||||
@@ -379,8 +307,9 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
</Space>
|
||||
</div>
|
||||
<Form form={form}>
|
||||
<Form.Item name="default_model_config_id" hidden noStyle></Form.Item>
|
||||
<Form.Item name="model_parameters" hidden noStyle></Form.Item>
|
||||
<Space size={16} direction="vertical" style={{ width: '100%' }}>
|
||||
{/* 提示词 */}
|
||||
<Card title={t('application.promptConfiguration')}>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-2.75">
|
||||
<div className="rb:font-medium rb:leading-5">
|
||||
@@ -406,36 +335,31 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
{/* 知识库 */}
|
||||
<Knowledge
|
||||
data={data?.knowledge_retrieval || { knowledge_bases: [] }}
|
||||
onUpdate={setKnowledgeConfig}
|
||||
/>
|
||||
<Form.Item name="knowledge_retrieval" noStyle>
|
||||
<Knowledge />
|
||||
</Form.Item>
|
||||
|
||||
{/* 记忆配置 */}
|
||||
<Card title={t('application.memoryConfiguration')}>
|
||||
<Space size={24} direction='vertical' style={{ width: '100%' }}>
|
||||
<SwitchWrapper title="dialogueHistoricalMemory" desc="dialogueHistoricalMemoryDesc" name="memoryEnabled" />
|
||||
<SwitchWrapper title="dialogueHistoricalMemory" desc="dialogueHistoricalMemoryDesc" name={['memory', 'enabled']} />
|
||||
<SelectWrapper
|
||||
title="selectMemoryContent"
|
||||
desc="selectMemoryContentDesc"
|
||||
name="memory_content"
|
||||
name={['memory', 'memory_content']}
|
||||
url={memoryConfigListUrl}
|
||||
/>
|
||||
</Space>
|
||||
</Card>
|
||||
|
||||
{/* 变量配置 */}
|
||||
<VariableList
|
||||
data={data?.variables}
|
||||
onUpdate={setVariableList}
|
||||
/>
|
||||
<Form.Item name="variables">
|
||||
<VariableList />
|
||||
</Form.Item>
|
||||
|
||||
{/* 工具配置 */}
|
||||
<ToolList
|
||||
data={data?.tools || []}
|
||||
onUpdate={setToolList}
|
||||
/>
|
||||
<Form.Item name="tools">
|
||||
<ToolList />
|
||||
</Form.Item>
|
||||
</Space>
|
||||
</Form>
|
||||
</Col>
|
||||
@@ -444,6 +368,9 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
{t('application.debuggingAndPreview')}
|
||||
|
||||
<Space size={10}>
|
||||
<Button type="primary" ghost onClick={handleOpenVariableConfig}>
|
||||
{t('application.variableConfig')}
|
||||
</Button>
|
||||
<Button type="primary" ghost onClick={handleAddModel}>
|
||||
+ {t('application.addModel')}
|
||||
</Button>
|
||||
@@ -463,7 +390,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
|
||||
<ModelConfigModal
|
||||
modelList={modelList}
|
||||
data={formData as Config}
|
||||
data={values}
|
||||
ref={modelConfigModalRef}
|
||||
refresh={refresh}
|
||||
/>
|
||||
@@ -472,6 +399,10 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
||||
defaultModel={defaultModel}
|
||||
refresh={updatePrompt}
|
||||
/>
|
||||
<ChatVariableConfigModal
|
||||
ref={chatVariableConfigModalRef}
|
||||
refresh={handleSaveChatVariable}
|
||||
/>
|
||||
</>
|
||||
);
|
||||
});
|
||||
|
||||
@@ -3,18 +3,21 @@ import RbCard from '@/components/RbCard/Card'
|
||||
|
||||
interface CardProps {
|
||||
title?: string | ReactNode;
|
||||
subTitle?: string | ReactNode;
|
||||
children: ReactNode;
|
||||
extra?: ReactNode;
|
||||
}
|
||||
|
||||
const Card: FC<CardProps> = ({
|
||||
title,
|
||||
subTitle,
|
||||
children,
|
||||
extra,
|
||||
}) => {
|
||||
return (
|
||||
<RbCard
|
||||
title={title}
|
||||
subTitle={subTitle}
|
||||
extra={extra}
|
||||
headerType="borderL"
|
||||
headerClassName="rb:before:bg-[#155EEF]! rb:before:h-[19px]"
|
||||
|
||||
@@ -0,0 +1,101 @@
|
||||
import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input, InputNumber } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ChatVariableConfigModalRef } from '../types'
|
||||
import type { Variable } from './VariableList/types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
|
||||
interface VariableEditModalProps {
|
||||
refresh: (values: Variable[]) => void;
|
||||
}
|
||||
|
||||
const ChatVariableConfigModal = forwardRef<ChatVariableConfigModalRef, VariableEditModalProps>(({
|
||||
refresh,
|
||||
}, ref) => {
|
||||
const { t } = useTranslation();
|
||||
const [visible, setVisible] = useState(false);
|
||||
const [form] = Form.useForm<{variables: Variable[]}>();
|
||||
const [loading, setLoading] = useState(false)
|
||||
const [initialValues, setInitialValues] = useState<Variable[]>([])
|
||||
|
||||
// 封装取消方法,添加关闭弹窗逻辑
|
||||
const handleClose = () => {
|
||||
setVisible(false);
|
||||
form.resetFields();
|
||||
setLoading(false)
|
||||
};
|
||||
|
||||
const handleOpen = (values: Variable[]) => {
|
||||
console.log('values', values)
|
||||
setVisible(true);
|
||||
form.setFieldsValue({variables: values})
|
||||
setInitialValues([...values])
|
||||
};
|
||||
// 封装保存方法,添加提交逻辑
|
||||
const handleSave = () => {
|
||||
form.validateFields().then((values) => {
|
||||
refresh([
|
||||
...(values?.variables ?? []),
|
||||
])
|
||||
handleClose()
|
||||
})
|
||||
}
|
||||
|
||||
// 暴露给父组件的方法
|
||||
useImperativeHandle(ref, () => ({
|
||||
handleOpen,
|
||||
handleClose
|
||||
}));
|
||||
|
||||
console.log(form.getFieldValue('variables'))
|
||||
|
||||
return (
|
||||
<RbModal
|
||||
title={t('application.variableConfig')}
|
||||
open={visible}
|
||||
onCancel={handleClose}
|
||||
okText={t('common.save')}
|
||||
onOk={handleSave}
|
||||
confirmLoading={loading}
|
||||
>
|
||||
<Form
|
||||
form={form}
|
||||
layout="horizontal"
|
||||
scrollToFirstError={{ behavior: 'instant', block: 'end', focus: true }}
|
||||
>
|
||||
<Form.List name="variables">
|
||||
{(fields) => (
|
||||
<>
|
||||
{fields.map(({ name }, index) => {
|
||||
const field = initialValues[index]
|
||||
return (
|
||||
<Form.Item
|
||||
key={name}
|
||||
name={[name, 'value']}
|
||||
label={`${field.name}·${field.display_name}`}
|
||||
rules={[
|
||||
{ required: field.required, message: t('common.pleaseEnter') },
|
||||
]}
|
||||
>
|
||||
{
|
||||
field.type === 'text' && <Input placeholder={t('common.pleaseEnter')} />
|
||||
}
|
||||
{
|
||||
field.type === 'number' && <InputNumber placeholder={t('common.pleaseEnter')} className="rb:w-full!" onChange={(value) => form.setFieldValue(['variables', name, 'value'], value)} />
|
||||
}
|
||||
{
|
||||
field.type === 'paragraph' && <Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||
}
|
||||
</Form.Item>
|
||||
)
|
||||
})}
|
||||
</>
|
||||
)}
|
||||
</Form.List>
|
||||
</Form>
|
||||
</RbModal>
|
||||
);
|
||||
});
|
||||
|
||||
export default ChatVariableConfigModal;
|
||||
@@ -2,7 +2,6 @@ import { type FC, useRef, useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Space, Button, List } from 'antd'
|
||||
import knowledgeEmpty from '@/assets/images/application/knowledgeEmpty.svg'
|
||||
import Card from './Card'
|
||||
import type {
|
||||
KnowledgeConfigForm,
|
||||
KnowledgeConfig,
|
||||
@@ -11,14 +10,16 @@ import type {
|
||||
KnowledgeModalRef,
|
||||
KnowledgeConfigModalRef,
|
||||
KnowledgeGlobalConfigModalRef,
|
||||
} from '../types'
|
||||
} from './types'
|
||||
import Empty from '@/components/Empty'
|
||||
import KnowledgeListModal from './KnowledgeListModal'
|
||||
import KnowledgeConfigModal from './KnowledgeConfigModal'
|
||||
import KnowledgeGlobalConfigModal from './KnowledgeGlobalConfigModal'
|
||||
import Tag from '@/components/Tag'
|
||||
import { getKnowledgeBaseList } from '@/api/knowledgeBase'
|
||||
import Card from '../Card'
|
||||
|
||||
const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig) => void}> = ({data, onUpdate}) => {
|
||||
const Knowledge: FC<{value?: KnowledgeConfig; onChange?: (config: KnowledgeConfig) => void}> = ({value = {knowledge_bases: []}, onChange}) => {
|
||||
const { t } = useTranslation()
|
||||
const knowledgeModalRef = useRef<KnowledgeModalRef>(null)
|
||||
const knowledgeConfigModalRef = useRef<KnowledgeConfigModalRef>(null)
|
||||
@@ -27,12 +28,31 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
||||
const [editConfig, setEditConfig] = useState<KnowledgeConfig>({} as KnowledgeConfig)
|
||||
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
setEditConfig({ ...(data || {}) })
|
||||
const knowledge_bases = [...(data.knowledge_bases || [])]
|
||||
setKnowledgeList(knowledge_bases)
|
||||
if (value && JSON.stringify(value) !== JSON.stringify(editConfig)) {
|
||||
setEditConfig({ ...(value || {}) })
|
||||
const knowledge_bases = [...(value.knowledge_bases || [])]
|
||||
|
||||
// 检查是否有knowledge_bases缺少name字段
|
||||
const basesWithoutName = knowledge_bases.filter(base => !base.name)
|
||||
if (basesWithoutName.length > 0) {
|
||||
// 调用接口获取完整的知识库信息
|
||||
getKnowledgeBaseList().then(res => {
|
||||
const fullBases = knowledge_bases.map(base => {
|
||||
if (!base.name) {
|
||||
const fullBase = res.items.find((item: any) => item.id === base.kb_id)
|
||||
return fullBase ? { ...base, ...fullBase } : base
|
||||
}
|
||||
return base
|
||||
})
|
||||
setKnowledgeList(fullBases)
|
||||
}).catch(() => {
|
||||
setKnowledgeList(knowledge_bases)
|
||||
})
|
||||
} else {
|
||||
setKnowledgeList(knowledge_bases)
|
||||
}
|
||||
}
|
||||
}, [data])
|
||||
}, [value])
|
||||
|
||||
const handleKnowledgeConfig = () => {
|
||||
knowledgeGlobalConfigModalRef.current?.handleOpen()
|
||||
@@ -43,7 +63,7 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
||||
const handleDeleteKnowledge = (id: string) => {
|
||||
const list = knowledgeList.filter(item => item.id !== id)
|
||||
setKnowledgeList([...list])
|
||||
onUpdate({
|
||||
onChange && onChange({
|
||||
...editConfig,
|
||||
knowledge_bases: [...list],
|
||||
})
|
||||
@@ -65,7 +85,7 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
||||
list = [...values as KnowledgeBase[]]
|
||||
}
|
||||
setKnowledgeList([...list])
|
||||
onUpdate({
|
||||
onChange && onChange({
|
||||
...editConfig,
|
||||
knowledge_bases: [...list],
|
||||
})
|
||||
@@ -77,14 +97,14 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
||||
config: {...values as KnowledgeConfigForm}
|
||||
}
|
||||
setKnowledgeList([...list])
|
||||
onUpdate({
|
||||
onChange && onChange({
|
||||
...editConfig,
|
||||
knowledge_bases: [...list],
|
||||
})
|
||||
} else if (type === 'rerankerConfig') {
|
||||
const rerankerValues = values as RerankerConfig
|
||||
setEditConfig(prev => ({ ...prev, ...rerankerValues }))
|
||||
onUpdate({
|
||||
onChange && onChange({
|
||||
...editConfig,
|
||||
...rerankerValues,
|
||||
reranker_id: rerankerValues.rerank_model ? rerankerValues.reranker_id : undefined,
|
||||
@@ -93,55 +113,54 @@ const Knowledge: FC<{data: KnowledgeConfig; onUpdate: (config: KnowledgeConfig)
|
||||
}
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
<Card
|
||||
title={t('application.knowledgeBaseAssociation')}
|
||||
extra={
|
||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={() => handleKnowledgeConfig()}>{t('application.globalConfig')}</Button>
|
||||
<Space>
|
||||
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleKnowledgeConfig}>{t('workflow.config.knowledge-retrieval.recallConfig')}</Button>
|
||||
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddKnowledge}>+</Button>
|
||||
</Space>
|
||||
}
|
||||
>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-3">
|
||||
<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>
|
||||
</div>
|
||||
|
||||
{knowledgeList.length === 0
|
||||
? <Empty url={knowledgeEmpty} size={88} subTitle={t('application.knowledgeEmpty')} />
|
||||
:
|
||||
<List
|
||||
grid={{ gutter: 12, column: 1 }}
|
||||
dataSource={knowledgeList}
|
||||
renderItem={(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-lg">
|
||||
<div className="rb:font-medium rb:leading-4">
|
||||
{item.name}
|
||||
<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')}
|
||||
</Tag>
|
||||
<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>
|
||||
renderItem={(item) => {
|
||||
if (!item.id) return null
|
||||
return (
|
||||
<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-lg">
|
||||
<div className="rb:font-medium rb:leading-4">
|
||||
{item.name}
|
||||
<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')}
|
||||
</Tag>
|
||||
<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>
|
||||
<Space size={12}>
|
||||
<div
|
||||
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)}
|
||||
></div>
|
||||
<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={() => handleDeleteKnowledge(item.id)}
|
||||
></div>
|
||||
</Space>
|
||||
</div>
|
||||
<Space size={12}>
|
||||
<div
|
||||
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)}
|
||||
></div>
|
||||
<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={() => handleDeleteKnowledge(item.id)}
|
||||
></div>
|
||||
</Space>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
</List.Item>
|
||||
)
|
||||
}}
|
||||
/>
|
||||
}
|
||||
{/* 全局设置 */}
|
||||
<KnowledgeGlobalConfigModal
|
||||
data={editConfig}
|
||||
ref={knowledgeGlobalConfigModalRef}
|
||||
refresh={refresh}
|
||||
/>
|
||||
{/* 知识库列表 */}
|
||||
<KnowledgeListModal
|
||||
ref={knowledgeModalRef}
|
||||
selectedList={knowledgeList}
|
||||
@@ -2,7 +2,7 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Select, InputNumber } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { KnowledgeConfigModalRef, KnowledgeBase, KnowledgeConfigForm } from '../types'
|
||||
import type { KnowledgeConfigModalRef, KnowledgeBase, KnowledgeConfigForm, RetrieveType } from './types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import RbSlider from '@/components/RbSlider'
|
||||
import { formatDateTime } from '@/utils/format';
|
||||
@@ -12,7 +12,7 @@ const FormItem = Form.Item;
|
||||
interface KnowledgeConfigModalProps {
|
||||
refresh: (values: KnowledgeConfigForm, type: 'knowledgeConfig') => void;
|
||||
}
|
||||
const retrieveTypes = ['participle', 'semantic', 'hybrid']
|
||||
const retrieveTypes: RetrieveType[] = ['participle', 'semantic', 'hybrid']
|
||||
|
||||
const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfigModalProps>(({
|
||||
refresh,
|
||||
@@ -33,8 +33,11 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
|
||||
const handleOpen = (data: KnowledgeBase) => {
|
||||
form.setFieldsValue({
|
||||
retrieve_type: retrieveTypes[0],
|
||||
retrieve_type: data?.config?.retrieve_type || retrieveTypes[0],
|
||||
kb_id: data.id,
|
||||
top_k: data?.config?.top_k || 5,
|
||||
similarity_threshold: data?.config?.similarity_threshold || 0.5,
|
||||
vector_similarity_weight: data?.config?.vector_similarity_weight || 0.5,
|
||||
...(data || {}),
|
||||
...(data?.config || {}),
|
||||
})
|
||||
@@ -62,12 +65,10 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
|
||||
useEffect(() => {
|
||||
if (values?.retrieve_type) {
|
||||
const initialValues = Object.keys(values).map(key => {
|
||||
return {
|
||||
[key as keyof KnowledgeConfigForm]: (key === 'kb_id' || key === 'retrieve_type') ? values[key] : undefined
|
||||
}
|
||||
})
|
||||
form.resetFields(initialValues)
|
||||
const fieldsToReset = Object.keys(values).filter(key =>
|
||||
key !== 'kb_id' && key !== 'retrieve_type'
|
||||
) as (keyof KnowledgeConfigForm)[];
|
||||
form.resetFields(fieldsToReset);
|
||||
}
|
||||
}, [values?.retrieve_type])
|
||||
|
||||
@@ -84,12 +85,12 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
layout="vertical"
|
||||
>
|
||||
{data && (
|
||||
<div className="rb:mb-[24px] rb:flex rb:items-center rb:justify-between rb:border rb:rounded-[8px] rb:p-[17px_16px] rb:cursor-pointer rb:bg-[#F0F3F8] rb:border-[#DFE4ED] rb:text-[#212332]">
|
||||
<div className="rb:text-[16px] rb:leading-[22px]">
|
||||
<div className="rb:mb-6 rb:flex rb:items-center rb:justify-between rb:border rb:rounded-lg rb:p-[17px_16px] rb:cursor-pointer rb:bg-[#F0F3F8] rb:border-[#DFE4ED] rb:text-[#212332]">
|
||||
<div className="rb:text-[16px] rb:leading-5.5">
|
||||
{data.name}
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167] rb:mt-[8px]">{t('application.contains', {include_count: data.doc_num})}</div>
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167] rb:mt-2">{t('application.contains', {include_count: data.doc_num})}</div>
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167]">{formatDateTime(data.updated_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167]">{formatDateTime(data.updated_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
||||
</div>
|
||||
)}
|
||||
<FormItem name="kb_id" hidden />
|
||||
@@ -114,8 +115,14 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
label={t('application.top_k')}
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
extra={t('application.top_k_desc')}
|
||||
initialValue={5}
|
||||
>
|
||||
<InputNumber style={{ width: '100%' }} />
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
min={1}
|
||||
max={20}
|
||||
onChange={(value) => form.setFieldValue('top_k', value)}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* 语义相似度阈值 similarity_threshold */}
|
||||
{values?.retrieve_type === 'semantic' && (
|
||||
@@ -123,6 +130,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
@@ -137,6 +145,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
name="vector_similarity_weight"
|
||||
label={t('application.vector_similarity_weight')}
|
||||
extra={t('application.vector_similarity_weight_desc')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
@@ -152,6 +161,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
name="similarity_threshold"
|
||||
label={t('application.similarity_threshold')}
|
||||
extra={t('application.similarity_threshold_desc1')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
@@ -163,6 +173,7 @@ const KnowledgeConfigModal = forwardRef<KnowledgeConfigModalRef, KnowledgeConfig
|
||||
name="vector_similarity_weight"
|
||||
label={t('application.vector_similarity_weight')}
|
||||
extra={t('application.vector_similarity_weight_desc1')}
|
||||
initialValue={0.5}
|
||||
>
|
||||
<RbSlider
|
||||
max={1.0}
|
||||
@@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useState, useEffect } from 'react';
|
||||
import { Form, InputNumber, Switch } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from '../types'
|
||||
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from './types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import CustomSelect from '@/components/CustomSelect'
|
||||
import { getModelListUrl } from '@/api/models'
|
||||
@@ -71,18 +71,18 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
|
||||
form={form}
|
||||
layout="vertical"
|
||||
>
|
||||
<div className="rb:text-[#5B6167] rb:mb-[24px]">{t('application.globalConfigDesc')}</div>
|
||||
<div className="rb:text-[#5B6167] rb:mb-6">{t('application.globalConfigDesc')}</div>
|
||||
|
||||
{/* 结果重排 */}
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:my-[24px]">
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-[20px]">
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:my-6">
|
||||
<div className="rb:text-[14px] rb:font-medium rb:leading-5">
|
||||
{t('application.rerankModel')}
|
||||
<div className="rb:mt-[4px] rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-[16px]">{t('application.rerankModelDesc')}</div>
|
||||
<div className="rb:mt-1 rb:text-[12px] rb:text-[#5B6167] rb:font-regular rb:leading-4">{t('application.rerankModelDesc')}</div>
|
||||
</div>
|
||||
<FormItem
|
||||
name="rerank_model"
|
||||
valuePropName="checked"
|
||||
className="rb:mb-[0px]!"
|
||||
className="rb:mb-0!"
|
||||
>
|
||||
<Switch />
|
||||
</FormItem>
|
||||
@@ -110,7 +110,12 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
|
||||
rules={[{ required: true, message: t('common.pleaseEnter') }]}
|
||||
extra={t('application.reranker_top_k_desc')}
|
||||
>
|
||||
<InputNumber style={{ width: '100%' }} min={1} max={20} />
|
||||
<InputNumber
|
||||
style={{ width: '100%' }}
|
||||
min={1}
|
||||
max={20}
|
||||
onChange={(value) => form.setFieldValue('reranker_top_k', value)}
|
||||
/>
|
||||
</FormItem>
|
||||
</>}
|
||||
</Form>
|
||||
@@ -2,7 +2,7 @@ import { forwardRef, useEffect, useImperativeHandle, useState } from 'react';
|
||||
import { Space, List } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
import clsx from 'clsx'
|
||||
import type { KnowledgeModalRef, KnowledgeBase } from '../types'
|
||||
import type { KnowledgeModalRef, KnowledgeBase } from './types'
|
||||
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import { getKnowledgeBaseList } from '@/api/knowledgeBase'
|
||||
@@ -39,12 +39,13 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
||||
setQuery({})
|
||||
setSelectedIds([])
|
||||
setSelectedRows([])
|
||||
getList()
|
||||
};
|
||||
|
||||
useEffect(() => {
|
||||
getList()
|
||||
}, [query.keywords])
|
||||
if (visible) {
|
||||
getList()
|
||||
}
|
||||
}, [query.keywords, visible])
|
||||
const getList = () => {
|
||||
getKnowledgeBaseList(undefined, {
|
||||
...query,
|
||||
@@ -124,15 +125,15 @@ const KnowledgeListModal = forwardRef<KnowledgeModalRef, KnowledgeModalProps>(({
|
||||
dataSource={filterList}
|
||||
renderItem={(item: KnowledgeBase) => (
|
||||
<List.Item>
|
||||
<div key={item.id} className={clsx("rb:flex rb:items-center rb:justify-between rb:border rb:rounded-[8px] rb:p-[17px_16px] rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
|
||||
<div key={item.id} className={clsx("rb:flex rb:items-center rb:justify-between rb:border rb:rounded-lg rb:p-[17px_16px] rb:cursor-pointer rb:hover:bg-[#F0F3F8]", {
|
||||
"rb:bg-[rgba(21,94,239,0.06)] rb:border-[#155EEF] rb:text-[#155EEF]": selectedIds.includes(item.id),
|
||||
"rb:border-[#DFE4ED] rb:text-[#212332]": !selectedIds.includes(item.id),
|
||||
})} onClick={() => handleSelect(item)}>
|
||||
<div className="rb:text-[16px] rb:leading-[22px]">
|
||||
<div className="rb:text-[16px] rb:leading-5.5">
|
||||
{item.name}
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167] rb:mt-[8px]">{t('application.contains', {include_count: item.doc_num})}</div>
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167] rb:mt-2">{t('application.contains', {include_count: item.doc_num})}</div>
|
||||
</div>
|
||||
<div className="rb:text-[12px] rb:leading-[16px] rb:text-[#5B6167]">{formatDateTime(item.created_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
||||
<div className="rb:text-[12px] rb:leading-4 rb:text-[#5B6167]">{formatDateTime(item.created_at, 'YYYY-MM-DD HH:mm:ss')}</div>
|
||||
</div>
|
||||
</List.Item>
|
||||
)}
|
||||
@@ -0,0 +1,30 @@
|
||||
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
||||
export interface RerankerConfig {
|
||||
rerank_model?: boolean | undefined;
|
||||
reranker_id?: string | undefined;
|
||||
reranker_top_k?: number | undefined;
|
||||
}
|
||||
export type RetrieveType = 'participle' | 'semantic' | 'hybrid'
|
||||
export interface KnowledgeConfigForm {
|
||||
kb_id?: string;
|
||||
similarity_threshold?: number;
|
||||
vector_similarity_weight?: number;
|
||||
top_k?: number;
|
||||
retrieve_type?: RetrieveType;
|
||||
}
|
||||
export interface KnowledgeBase extends KnowledgeBaseListItem, KnowledgeConfigForm {
|
||||
config?: KnowledgeConfigForm
|
||||
}
|
||||
export interface KnowledgeConfig extends RerankerConfig {
|
||||
knowledge_bases: KnowledgeBase[];
|
||||
}
|
||||
|
||||
export interface KnowledgeConfigModalRef {
|
||||
handleOpen: (data: KnowledgeBase) => void;
|
||||
}
|
||||
export interface KnowledgeGlobalConfigModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
export interface KnowledgeModalRef {
|
||||
handleOpen: (config?: KnowledgeConfig[]) => void;
|
||||
}
|
||||
@@ -1,22 +1,22 @@
|
||||
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 Card from '../Card'
|
||||
import type {
|
||||
ToolModalRef,
|
||||
ToolOption
|
||||
} from '../types'
|
||||
} 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 ToolList: FC<{ value?: ToolOption[]; onChange?: (config: ToolOption[]) => void}> = ({value, onChange}) => {
|
||||
const { t } = useTranslation()
|
||||
const toolModalRef = useRef<ToolModalRef>(null)
|
||||
const [toolList, setToolList] = useState<ToolOption[]>([])
|
||||
useEffect(() => {
|
||||
if (data) {
|
||||
const processedData = data.map(async (item) => {
|
||||
if (value) {
|
||||
const processedData = value.map(async (item) => {
|
||||
if (!item.label && item.tool_id) {
|
||||
try {
|
||||
const [toolDetail, methods] = await Promise.all([
|
||||
@@ -77,7 +77,7 @@ const ToolList: FC<{ data: ToolOption[]; onUpdate: (config: ToolOption[]) => voi
|
||||
|
||||
Promise.all(processedData).then(setToolList)
|
||||
}
|
||||
}, [data])
|
||||
}, [value])
|
||||
|
||||
const handleAddTool = () => {
|
||||
toolModalRef.current?.handleOpen()
|
||||
@@ -85,12 +85,12 @@ const ToolList: FC<{ data: ToolOption[]; onUpdate: (config: ToolOption[]) => voi
|
||||
const updateTools = (tool: ToolOption) => {
|
||||
const list = [...toolList, tool]
|
||||
setToolList(list)
|
||||
onUpdate(list)
|
||||
onChange && onChange(list)
|
||||
}
|
||||
const handleDeleteTool = (index: number) => {
|
||||
const list = toolList.filter((_item, idx) => idx !== index)
|
||||
setToolList([...list])
|
||||
onUpdate(list)
|
||||
onChange && onChange(list)
|
||||
}
|
||||
const handleChangeEnabled = (index: number) => {
|
||||
const list = toolList.map((item, idx) => {
|
||||
@@ -103,7 +103,7 @@ const ToolList: FC<{ data: ToolOption[]; onUpdate: (config: ToolOption[]) => voi
|
||||
return item
|
||||
})
|
||||
setToolList([...list])
|
||||
onUpdate(list)
|
||||
onChange && onChange(list)
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
@@ -112,7 +112,6 @@ const ToolList: FC<{ data: ToolOption[]; onUpdate: (config: ToolOption[]) => voi
|
||||
<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddTool}>+{t('application.addTool')}</Button>
|
||||
}
|
||||
>
|
||||
|
||||
{toolList.length === 0
|
||||
? <Empty size={88} />
|
||||
:
|
||||
26
web/src/views/ApplicationConfig/components/ToolList/types.ts
Normal file
26
web/src/views/ApplicationConfig/components/ToolList/types.ts
Normal file
@@ -0,0 +1,26 @@
|
||||
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;
|
||||
}
|
||||
export interface ToolModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
@@ -1,131 +0,0 @@
|
||||
import { type FC, useRef, useState, useEffect } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Space, Button, Switch } from 'antd'
|
||||
import variablesEmpty from '@/assets/images/application/variablesEmpty.svg'
|
||||
import Card from './Card'
|
||||
import Table from '@/components/Table';
|
||||
import type { Variable, VariableEditModalRef } from '../types'
|
||||
import Empty from '@/components/Empty'
|
||||
import VariableEditModal from './VariableEditModal'
|
||||
|
||||
interface VariableListProps {
|
||||
data?: Variable[];
|
||||
onUpdate: (data: Variable[]) => void;
|
||||
}
|
||||
const VariableList: FC<VariableListProps> = ({data = [], onUpdate}) => {
|
||||
const { t } = useTranslation()
|
||||
const variableEditModalRef = useRef<VariableEditModalRef>(null)
|
||||
const [variableList, setVariableList] = useState<Variable[]>([])
|
||||
const [maxIndex, setMaxIndex] = useState(0)
|
||||
|
||||
useEffect(() => {
|
||||
if (!data || data.length === 0) return
|
||||
const list = data.map((item, index) => ({
|
||||
...item,
|
||||
index
|
||||
}))
|
||||
setVariableList(list)
|
||||
onUpdate(list)
|
||||
setMaxIndex(list.length)
|
||||
}, [data])
|
||||
|
||||
const handleAddVariable = () => {
|
||||
variableEditModalRef.current?.handleOpen()
|
||||
}
|
||||
const handleSaveVariable = (value: Variable) => {
|
||||
if (value.index !== undefined && value.index >= 0) {
|
||||
const index = variableList.findIndex(item => item.index === value.index)
|
||||
if (index !== -1) {
|
||||
const newData = [...variableList]
|
||||
newData[index] = value
|
||||
setVariableList([...newData])
|
||||
onUpdate([...newData])
|
||||
}
|
||||
} else {
|
||||
const list = [...variableList, {
|
||||
index: maxIndex + 1,
|
||||
...value
|
||||
}]
|
||||
setVariableList(list)
|
||||
onUpdate([...list])
|
||||
setMaxIndex(maxIndex + 1)
|
||||
}
|
||||
}
|
||||
const handleDeleteVariable = (index: number) => {
|
||||
const list = variableList.filter((_, i) => i !== index)
|
||||
setVariableList(list)
|
||||
onUpdate([...list])
|
||||
}
|
||||
return (
|
||||
<Card title={t('application.variableConfiguration')}>
|
||||
<div className="rb:flex rb:items-center rb:justify-between rb:mb-[11px]">
|
||||
<div className="rb:font-medium rb:leading-[20px]">
|
||||
{t('application.VariableManagement')}
|
||||
<span className="rb:font-regular rb:text-[12px] rb:text-[#5B6167]"> ({t('application.VariableManagementDesc')})</span>
|
||||
</div>
|
||||
<Button style={{padding: '0 8px', height: '24px'}} onClick={handleAddVariable}>+{t('application.addVariables')}</Button>
|
||||
</div>
|
||||
|
||||
{/* List */}
|
||||
{variableList.length > 0
|
||||
? (
|
||||
<div className="rb:mt-[12px]">
|
||||
<Table
|
||||
rowKey="index"
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
title: t('application.variableType'),
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
render: (type) => t(`application.${type}`)
|
||||
},
|
||||
{
|
||||
title: t('application.variableKey'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: t('application.variableName'),
|
||||
dataIndex: 'display_name',
|
||||
key: 'display_name',
|
||||
},
|
||||
{
|
||||
title: t('application.optional'),
|
||||
dataIndex: 'required',
|
||||
key: 'required',
|
||||
render: (required) => <Switch checked={!required} disabled />
|
||||
},
|
||||
{
|
||||
title: t('common.operation'),
|
||||
key: 'action',
|
||||
render: (_, record, index: number) => (
|
||||
<Space size="middle">
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => variableEditModalRef.current?.handleOpen(record as Variable)}
|
||||
>
|
||||
{t('common.edit')}
|
||||
</Button>
|
||||
<Button type="link" danger onClick={() => handleDeleteVariable(index)}>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
initialData={variableList as unknown as Record<string, unknown>[]}
|
||||
emptySize={88}
|
||||
/>
|
||||
</div>
|
||||
)
|
||||
: <Empty url={variablesEmpty} size={88} subTitle={t('application.variablesEmpty')} />
|
||||
}
|
||||
<VariableEditModal
|
||||
ref={variableEditModalRef}
|
||||
refreshTable={handleSaveVariable}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
export default VariableList
|
||||
@@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useState } from 'react';
|
||||
import { Form, Input } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ApiExtensionModalData, ApiExtensionModalRef } from '../types'
|
||||
import type { ApiExtensionModalData, ApiExtensionModalRef } from './types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
|
||||
const FormItem = Form.Item;
|
||||
@@ -2,7 +2,7 @@ import { forwardRef, useImperativeHandle, useState, useRef } from 'react';
|
||||
import { Form, Input, Select, InputNumber, Checkbox, Tag, Divider, Button } from 'antd';
|
||||
import { useTranslation } from 'react-i18next';
|
||||
|
||||
import type { ApiExtensionModalRef, Variable, VariableEditModalRef } from '../types'
|
||||
import type { ApiExtensionModalRef, Variable, VariableEditModalRef } from './types'
|
||||
import RbModal from '@/components/RbModal'
|
||||
import SortableList from '@/components/SortableList'
|
||||
import ApiExtensionModal from './ApiExtensionModal'
|
||||
@@ -137,7 +137,14 @@ const VariableEditModal = forwardRef<VariableEditModalRef, VariableEditModalProp
|
||||
{ pattern: /^[a-zA-Z_][a-zA-Z0-9_]*$/, message: t('application.invalidVariableName') },
|
||||
]}
|
||||
>
|
||||
<Input placeholder={t('common.enter')} />
|
||||
<Input
|
||||
placeholder={t('common.enter')}
|
||||
onBlur={(e) => {
|
||||
if (!form.getFieldValue('display_name')) {
|
||||
form.setFieldValue('display_name', e.target.value)
|
||||
}
|
||||
}}
|
||||
/>
|
||||
</FormItem>
|
||||
{/* 显示名称 */}
|
||||
<FormItem
|
||||
@@ -0,0 +1,110 @@
|
||||
import { type FC, useRef } from 'react'
|
||||
import { useTranslation } from 'react-i18next'
|
||||
import { Space, Button, Switch, Form } from 'antd'
|
||||
import variablesEmpty from '@/assets/images/application/variablesEmpty.svg'
|
||||
import Card from '../Card'
|
||||
import Table from '@/components/Table';
|
||||
import type { Variable, VariableEditModalRef } from './types'
|
||||
import Empty from '@/components/Empty'
|
||||
import VariableEditModal from './VariableEditModal'
|
||||
|
||||
interface VariableListProps {
|
||||
value?: Variable[];
|
||||
onChange?: (value: Variable[]) => void;
|
||||
}
|
||||
const VariableList: FC<VariableListProps> = ({value = [], onChange}) => {
|
||||
const { t } = useTranslation()
|
||||
const variableEditModalRef = useRef<VariableEditModalRef>(null)
|
||||
|
||||
const handleAddVariable = () => {
|
||||
variableEditModalRef.current?.handleOpen()
|
||||
}
|
||||
const handleSaveVariable = (variable: Variable) => {
|
||||
const newList = [...(value || [])]
|
||||
if (variable.index !== undefined && variable.index >= 0) {
|
||||
const index = newList.findIndex(item => item.index === variable.index)
|
||||
if (index !== -1) {
|
||||
newList[index] = variable
|
||||
}
|
||||
} else {
|
||||
newList.push({ ...variable, index: Date.now() })
|
||||
}
|
||||
onChange?.(newList)
|
||||
}
|
||||
return (
|
||||
<Card
|
||||
title={<>
|
||||
{t('application.variableConfiguration')}
|
||||
<span className="rb:font-regular rb:text-[12px] rb:text-[#5B6167]"> ({t('application.VariableManagementDesc')})</span>
|
||||
</>}
|
||||
extra={<Button style={{ padding: '0 8px', height: '24px' }} onClick={handleAddVariable}>+ {t('application.addVariables')}</Button>}
|
||||
>
|
||||
<Form.List name="variables" initialValue={value}>
|
||||
{(fields, { remove }) => {
|
||||
return (
|
||||
<>
|
||||
{fields.length > 0 ? (
|
||||
<div className="rb:mt-3">
|
||||
<Table
|
||||
rowKey="index"
|
||||
pagination={false}
|
||||
columns={[
|
||||
{
|
||||
title: t('application.variableType'),
|
||||
dataIndex: 'type',
|
||||
key: 'type',
|
||||
render: (type) => t(`application.${type}`)
|
||||
},
|
||||
{
|
||||
title: t('application.variableKey'),
|
||||
dataIndex: 'name',
|
||||
key: 'name',
|
||||
},
|
||||
{
|
||||
title: t('application.variableName'),
|
||||
dataIndex: 'display_name',
|
||||
key: 'display_name',
|
||||
},
|
||||
{
|
||||
title: t('application.optional'),
|
||||
dataIndex: 'required',
|
||||
key: 'required',
|
||||
render: (required) => <Switch checked={!required} disabled />
|
||||
},
|
||||
{
|
||||
title: t('common.operation'),
|
||||
key: 'action',
|
||||
render: (_, record, index: number) => (
|
||||
<Space size="middle">
|
||||
<Button
|
||||
type="link"
|
||||
onClick={() => variableEditModalRef.current?.handleOpen(record as Variable)}
|
||||
>
|
||||
{t('common.edit')}
|
||||
</Button>
|
||||
<Button type="link" danger onClick={() => remove(index)}>
|
||||
{t('common.delete')}
|
||||
</Button>
|
||||
</Space>
|
||||
),
|
||||
},
|
||||
]}
|
||||
initialData={value as unknown as Record<string, unknown>[]}
|
||||
emptySize={88}
|
||||
/>
|
||||
</div>
|
||||
) : (
|
||||
<Empty url={variablesEmpty} size={88} subTitle={t('application.variablesEmpty')} />
|
||||
)}
|
||||
</>
|
||||
)
|
||||
}}
|
||||
</Form.List>
|
||||
<VariableEditModal
|
||||
ref={variableEditModalRef}
|
||||
refreshTable={handleSaveVariable}
|
||||
/>
|
||||
</Card>
|
||||
)
|
||||
}
|
||||
export default VariableList
|
||||
@@ -0,0 +1,28 @@
|
||||
export interface Variable {
|
||||
index?: number;
|
||||
name: string;
|
||||
display_name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
max_length?: number;
|
||||
description?: string;
|
||||
|
||||
key?: string;
|
||||
default_value?: string;
|
||||
options?: string[];
|
||||
api_extension?: string;
|
||||
hidden?: boolean;
|
||||
value?: any;
|
||||
}
|
||||
export interface VariableEditModalRef {
|
||||
handleOpen: (values?: Variable) => void;
|
||||
}
|
||||
|
||||
export interface ApiExtensionModalData {
|
||||
name: string;
|
||||
apiEndpoint: string;
|
||||
apiKey: string;
|
||||
}
|
||||
export interface ApiExtensionModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
@@ -1,4 +1,6 @@
|
||||
import type { KnowledgeBaseListItem } from '@/views/KnowledgeBase/types'
|
||||
import type { KnowledgeConfig } from './components/Knowledge/types'
|
||||
import type { Variable } from './components/VariableList/types'
|
||||
import type { ToolOption } from './components/ToolList/types'
|
||||
import type { ChatItem } from '@/components/Chat/types'
|
||||
import type { GraphRef } from '@/views/Workflow/types';
|
||||
import type { ApiKey } from '@/views/ApiKeyManagement/types'
|
||||
@@ -14,55 +16,6 @@ export interface ModelConfig {
|
||||
n: number;
|
||||
stop?: string;
|
||||
}
|
||||
|
||||
/*************** 知识库相关 ******************/
|
||||
export interface RerankerConfig {
|
||||
rerank_model?: boolean | undefined;
|
||||
reranker_id?: string | undefined;
|
||||
reranker_top_k?: number | undefined;
|
||||
}
|
||||
export interface KnowledgeConfigForm {
|
||||
kb_id?: string;
|
||||
similarity_threshold?: number;
|
||||
vector_similarity_weight?: number;
|
||||
top_k?: number;
|
||||
retrieve_type?: 'participle' | 'semantic' | 'hybrid';
|
||||
}
|
||||
export interface KnowledgeBase extends KnowledgeBaseListItem, KnowledgeConfigForm {
|
||||
config?: KnowledgeConfigForm
|
||||
}
|
||||
export interface KnowledgeConfig extends RerankerConfig {
|
||||
knowledge_bases: KnowledgeBase[];
|
||||
}
|
||||
|
||||
export interface KnowledgeConfigModalRef {
|
||||
handleOpen: (data: KnowledgeBase) => void;
|
||||
}
|
||||
export interface KnowledgeGlobalConfigModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
/*********** end 知识库相关 ******************/
|
||||
|
||||
/*************** 变量相关 ******************/
|
||||
export interface Variable {
|
||||
index?: number;
|
||||
name: string;
|
||||
display_name: string;
|
||||
type: string;
|
||||
required: boolean;
|
||||
max_length?: number;
|
||||
description?: string;
|
||||
|
||||
key: string;
|
||||
default_value?: string;
|
||||
options?: string[];
|
||||
api_extension?: string;
|
||||
hidden?: boolean;
|
||||
}
|
||||
export interface VariableEditModalRef {
|
||||
handleOpen: (values?: Variable) => void;
|
||||
}
|
||||
/*********** end 变量相关 ******************/
|
||||
export interface MemoryConfig {
|
||||
enabled: boolean;
|
||||
memory_content?: string;
|
||||
@@ -131,17 +84,6 @@ export interface ModelConfigModalData {
|
||||
export interface AiPromptModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
export interface KnowledgeModalRef {
|
||||
handleOpen: (config?: KnowledgeConfig[]) => void;
|
||||
}
|
||||
export interface ApiExtensionModalData {
|
||||
name: string;
|
||||
apiEndpoint: string;
|
||||
apiKey: string;
|
||||
}
|
||||
export interface ApiExtensionModalRef {
|
||||
handleOpen: () => void;
|
||||
}
|
||||
export interface ChatData {
|
||||
label?: string;
|
||||
model_config_id?: string;
|
||||
@@ -206,30 +148,6 @@ export interface AiPromptForm {
|
||||
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;
|
||||
export interface ChatVariableConfigModalRef {
|
||||
handleOpen: (values: Variable[]) => void;
|
||||
}
|
||||
@@ -666,6 +666,77 @@ export const useWorkflowGraph = ({
|
||||
graphRef.current.resize(containerRef.current.offsetWidth, containerRef.current.offsetHeight);
|
||||
}
|
||||
};
|
||||
|
||||
const nodeChangePosition = ({ node, options }: { node: Node; options: { skipParentHandler?: boolean } }) => {
|
||||
const embedPadding = 50; // Define the embed padding constant
|
||||
if (options.skipParentHandler) {
|
||||
return
|
||||
}
|
||||
|
||||
const children = node.getChildren()
|
||||
if (children && children.length) {
|
||||
node.prop('originPosition', node.getPosition())
|
||||
}
|
||||
|
||||
const parent = node.getParent()
|
||||
if (parent && parent.isNode()) {
|
||||
let originSize = parent.prop('originSize')
|
||||
if (originSize == null) {
|
||||
originSize = parent.getSize()
|
||||
parent.prop('originSize', originSize)
|
||||
}
|
||||
|
||||
let originPosition = parent.prop('originPosition')
|
||||
if (originPosition == null) {
|
||||
originPosition = parent.getPosition()
|
||||
parent.prop('originPosition', originPosition)
|
||||
}
|
||||
|
||||
let x = originPosition.x
|
||||
let y = originPosition.y
|
||||
let cornerX = originPosition.x + originSize.width
|
||||
let cornerY = originPosition.y + originSize.height
|
||||
let hasChange = false
|
||||
|
||||
const children = parent.getChildren()
|
||||
if (children) {
|
||||
children.forEach((child) => {
|
||||
const bbox = child.getBBox().inflate(embedPadding)
|
||||
const corner = bbox.getCorner()
|
||||
|
||||
if (bbox.x < x) {
|
||||
x = bbox.x
|
||||
hasChange = true
|
||||
}
|
||||
|
||||
if (bbox.y < y) {
|
||||
y = bbox.y
|
||||
hasChange = true
|
||||
}
|
||||
|
||||
if (corner.x > cornerX) {
|
||||
cornerX = corner.x
|
||||
hasChange = true
|
||||
}
|
||||
|
||||
if (corner.y > cornerY) {
|
||||
cornerY = corner.y
|
||||
hasChange = true
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
if (hasChange) {
|
||||
parent.prop(
|
||||
{
|
||||
position: { x, y },
|
||||
size: { width: cornerX - x, height: cornerY - y },
|
||||
},
|
||||
{ skipParentHandler: true },
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// 初始化
|
||||
const init = () => {
|
||||
@@ -764,10 +835,7 @@ export const useWorkflowGraph = ({
|
||||
},
|
||||
},
|
||||
embedding: {
|
||||
enabled: true,
|
||||
validate (this) {
|
||||
return false
|
||||
}
|
||||
enabled: false,
|
||||
},
|
||||
translating: {
|
||||
restrict(view) {
|
||||
@@ -783,6 +851,17 @@ export const useWorkflowGraph = ({
|
||||
return null
|
||||
},
|
||||
},
|
||||
highlighting: {
|
||||
embedding: {
|
||||
name: 'stroke',
|
||||
args: {
|
||||
padding: -1,
|
||||
attrs: {
|
||||
stroke: '#73d13d',
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
});
|
||||
// 使用插件
|
||||
setupPlugins();
|
||||
@@ -824,7 +903,8 @@ export const useWorkflowGraph = ({
|
||||
// 监听缩放事件
|
||||
graphRef.current.on('scale', scaleEvent);
|
||||
// 监听节点移动事件
|
||||
graphRef.current.on('node:moved', nodeMoved);
|
||||
// graphRef.current.on('node:moved', nodeMoved);
|
||||
graphRef.current.on('node:change:position', nodeChangePosition);
|
||||
|
||||
// 监听画布变化事件
|
||||
const events = [
|
||||
|
||||
Reference in New Issue
Block a user