/* * @Author: ZhaoYing * @Date: 2026-02-03 16:29:21 * @Last Modified by: ZhaoYing * @Last Modified time: 2026-04-07 18:04:49 */ import { useEffect, useRef, useState, forwardRef, useImperativeHandle, useMemo } from 'react'; import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom'; import { Row, Col, Space, Form, Input, Button, App, Spin, Flex } from 'antd' import Chat from './components/Chat' import RbCard from '@/components/RbCard/Card' import Card from './components/Card' import ModelConfigModal from './components/ModelConfigModal' import type { ModelConfigModalRef, ChatData, Config, ModelConfig, AgentRef, MemoryConfig, AiPromptModalRef, Source, ChatVariableConfigModalRef, FeaturesConfigForm } 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/Knowledge' import VariableList from './components/VariableList/VariableList' import { getApplicationConfig } from '@/api/application' import { memoryConfigListUrl } from '@/api/memory' import CustomSelect from '@/components/CustomSelect' import AiPromptModal from './components/AiPromptModal' import ToolList from './components/ToolList/ToolList' import SkillList from './components/Skill' import ChatVariableConfigModal from './components/ChatVariableConfigModal'; import type { Skill } from '@/views/Skills/types' import SwitchFormItem from '@/components/FormItem/SwitchFormItem' import DescWrapper from '@/components/FormItem/DescWrapper' import FeaturesConfig from './components/FeaturesConfig' import { getListLogoUrl } from '@/views/ModelManagement/utils'; import type { ChatItem } from '@/components/Chat/types' export const replaceVariables = (statement: string, variables: Variable[]) => { return statement.replace(/\{\{([^}]+)\}\}/g, (match, name) => { const v = variables.find(item => item.name === name) return v?.value != null && v.value !== '' ? String(v.value) : match }) } /** * Agent configuration component * Manages single agent configuration including prompts, knowledge, memory, variables, and tools */ const Agent = forwardRef void }>(({ onFeaturesLoad }, ref) => { const { t } = useTranslation() const { id } = useParams(); const { message } = App.useApp() const [form] = Form.useForm() const [loading, setLoading] = useState(false) const [data, setData] = useState(null); const modelConfigModalRef = useRef(null) const [modelList, setModelList] = useState([]) const [defaultModel, setDefaultModel] = useState(null) const [chatList, setChatList] = useState([]) const values = Form.useWatch([], form) const [isSave, setIsSave] = useState(false) const initialized = useRef(false) // Initialization flag useEffect(() => { if (data) { initialized.current = true } }, [data]) useEffect(() => { if (!initialized.current) return if (isSave) return setIsSave(true) }, [values]) useEffect(() => { getModels() getData() }, [id]) /** * Fetch agent configuration data */ const getData = () => { setLoading(true) getApplicationConfig(id as string).then(res => { const response = res as Config const { skills, variables } = response const allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : [] const allTools = Array.isArray(response.tools) ? response.tools : [] const memoryContent = response.memory?.memory_config_id const parsedMemoryContent = memoryContent === null || memoryContent === '' ? undefined : !isNaN(Number(memoryContent)) ? Number(memoryContent) : memoryContent const variableList = variables?.map((item, index) => ({ ...item, index })) || [] form.setFieldsValue({ ...response, tools: allTools, memory: { ...response.memory, memory_config_id: parsedMemoryContent }, skills: { ...skills, skill_ids: allSkills }, variables: [...variableList] }) updateVariableList([...variableList]) setData({ ...response, tools: allTools }) onFeaturesLoad?.(response.features) }).finally(() => { setLoading(false) }) } /** * Refresh configuration after model changes * @param vo - Model configuration * @param type - Source type (model or chat) */ const refresh = (vo: ModelConfig, type: Source) => { if (type === 'model') { const { default_model_config_id, capability, ...rest } = vo if (default_model_config_id !== values.default_model_config_id) { const fileUpload = { ...values.features?.file_upload } Object.keys(fileUpload).forEach(key => { if (key.includes('enabled')) { (fileUpload as Record)[key] = false } }) form.setFieldValue(['features', 'file_upload'], fileUpload) message.warning(t('application.resetFeaturesTip')) } form.setFieldsValue({ default_model_config_id, capability, model_parameters: {...rest} }) if (default_model_config_id === values?.default_model_config_id) { const label = defaultModel?.id === default_model_config_id && defaultModel?.name ? defaultModel.name : vo.label || '' setChatList([{ label: label, model_config_id: default_model_config_id || '', model_parameters: {...rest}, list: [] }]) } } else if (type === 'chat') { if (chatList.length >= 4) { message.warning(t('application.maxChatCount')) return } const { label, default_model_config_id, ...reset } = vo setChatList((prev: ChatData[]) => { const newChatItem: ChatData = { label, model_config_id: default_model_config_id || '', model_parameters: {...reset}, list: [] }; return [ ...(prev || []).map(item => ({ ...item, conversation_id: undefined, list: [] })), newChatItem ]; }) } } /** * Open model configuration modal */ const handleModelConfig = () => { modelConfigModalRef.current?.handleOpen('model', { ...defaultModel, model_parameters : values?.model_parameters }) } /** * Clear all debugging chat sessions */ const handleClearDebugging = () => { setChatList([]) } /** * Save agent configuration * @param flag - Whether to show success message * @returns Promise that resolves when save is complete */ const handleSave = (flag = true) => { if (!isSave || !data) return Promise.resolve() const { memory, knowledge_retrieval, tools, skills, ...rest } = values const { knowledge_bases = [], ...knowledgeRest } = knowledge_retrieval || {} const { memory_config_id } = memory || {} // Get other necessary properties of memory from original data const originalMemory = data.memory || ({} as MemoryConfig) const params: Config = { ...data, ...rest, memory: { ...originalMemory, ...memory, memory_config_id: memory_config_id ? String(memory_config_id) : '', }, knowledge_retrieval: knowledge_bases.length > 0 ? { ...data.knowledge_retrieval, ...knowledgeRest, knowledge_bases: knowledge_bases.map(item => ({ kb_id: item.kb_id || item.id, retrieve_type: item.retrieve_type, top_k: item.top_k, similarity_threshold: item.similarity_threshold, vector_similarity_weight: item.vector_similarity_weight, // ...(item.config || {}) })) } as KnowledgeConfig : null, tools: tools.map(vo => { if (!vo.operation) { return { tool_id: vo.tool_id, enabled: vo.enabled } } return { tool_id: vo.tool_id, operation: vo.operation, enabled: vo.enabled } }), skills: { ...skills, skill_ids: (skills?.skill_ids as Skill[])?.map(vo => vo.id) } } return new Promise((resolve, reject) => { saveAgentConfig(data.app_id, params) .then((res) => { if (flag) { message.success({ content: t('common.saveSuccess'), duration: 1 }) } setIsSave(false) resolve(res) }).catch(error => { reject(error) }) }) } /** * Fetch available models list */ const getModels = () => { getModelList({ type: 'llm,chat', pagesize: 100, page: 1, is_active: true }) .then(res => { const response = res as { items: Model[] } setModelList(response.items) }) } /** * Add new model for debugging */ const handleAddModel = () => { modelConfigModalRef.current?.handleOpen('chat') } useEffect(() => { 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 || '', model_config_id: filterValue?.id || '', model_parameters: {...(values?.model_parameters || {})} as unknown as ModelConfig, list: [] }]) form.setFieldValue('capability', filterValue?.capability) } }, [modelList, values?.default_model_config_id]) useImperativeHandle(ref, () => ({ handleSave, features: values?.features })) const aiPromptModalRef = useRef(null) /** * Open AI prompt generation modal */ const handlePrompt = () => { aiPromptModalRef.current?.handleOpen() } /** * Update prompt and extract variables * @param value - New prompt value */ 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) } /** * Update variable list * @param list - New variable list */ const updateVariableList = (list: Variable[]) => { form.setFieldValue('variables', [...list]) setChatVariables([...list]) } const chatVariableConfigModalRef = useRef(null) const [chatVariables, setChatVariables] = useState([]) /** * Open chat variable configuration modal */ const handleOpenVariableConfig = () => { chatVariableConfigModalRef.current?.handleOpen(chatVariables) } /** * Save chat variable configuration * @param values - Variable values */ const handleSaveChatVariable = (variables: Variable[]) => { setChatVariables(variables) } useEffect(() => { setChatVariables(values?.variables || []) }, [values?.variables]) const handleSaveFeaturesConfig = (value: FeaturesConfigForm) => { form.setFieldValue('features', value) const { statement = '' } = value?.opening_statement || {} onFeaturesLoad?.(value) const usedVars = [...new Set([...(statement?.matchAll(/\{\{(\w+)\}\}/g) ?? [])].map(m => m[1]))] const variables = values?.variables const validNames = new Set(variables.map(v => v.name)) const invalid = usedVars.filter(v => !validNames.has(v)) if (invalid.length > 0) { const newVars = invalid.map((name, i) => ({ index: variables.length + i, name, display_name: name, type: 'text', required: true, max_length: 48, })) form.setFieldValue('variables', [...variables, ...newVars]) } } const modelLogo = useMemo(() => { return defaultModel?.name && getListLogoUrl(defaultModel.provider, defaultModel.logo as string) }, [defaultModel]) useEffect(() => { const opening_statement = form.getFieldValue(['features', 'opening_statement']) if (opening_statement?.enabled && opening_statement?.statement && opening_statement?.statement.trim() !== '') { const assistantMsg: ChatItem = { role: 'assistant', content: replaceVariables(opening_statement.statement, chatVariables), meta_data: { suggested_questions: opening_statement?.suggested_questions } } setChatList(prev => { if (prev.length === 0 && !defaultModel) return prev if (defaultModel && prev.length === 1) { return [{ label: defaultModel.name, model_config_id: defaultModel.id, model_parameters: defaultModel.config as unknown as ModelConfig, list: [assistantMsg] }] } return prev.map(vo => { if (vo.list?.length === 0) { return { ...vo, list: [assistantMsg] } } else if (vo.list && vo.list[0].role === 'assistant') { vo.list[0] = assistantMsg return { ...vo, list: [...vo.list] } } else { return { ...vo, list: [assistantMsg, ...(vo.list || [])] } } }) }) } }, [defaultModel, chatList.length, form.getFieldValue(['features', 'opening_statement']), chatVariables]) console.log('agent values', values) return ( <> {loading && }
{t('application.aiPrompt')} } >
{t('application.configuration')} ({t('application.configurationDesc')})
{/* Memory Configuration */} } layout="vertical" className="rb:mb-0!" > {/* Tool Configuration */}
} headerType="borderless" headerClassName="rb:h-[56px]! rb:leading-[22px]!" titleClassName="rb:font-[MiSans-Bold] rb:font-bold" bodyClassName="rb:p-4! rb:pt-0! rb:h-[calc(100%-56px)]!" className="rb:h-full!" >
); }); export default Agent;