From 24d68de98c9b149f2e5e377e2654f136942c9f62 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Wed, 31 Dec 2025 12:30:36 +0800 Subject: [PATCH] feat(web): cluster --- web/src/i18n/en.ts | 45 +++-- web/src/i18n/zh.ts | 46 ++++- web/src/views/ApplicationConfig/Agent.tsx | 6 +- web/src/views/ApplicationConfig/Api.tsx | 1 + web/src/views/ApplicationConfig/Cluster.tsx | 172 +++++++++++------- .../ApplicationConfig/components/Chat.tsx | 8 +- web/src/views/ApplicationConfig/types.ts | 28 ++- 7 files changed, 207 insertions(+), 99 deletions(-) diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 8fb8c58c..25f7b026 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -725,6 +725,14 @@ export const en = { quicklyForget: 'Quickly forget', slowForgetting: 'Slow forgetting', currentConfig: 'Current Config', + + decay_constant: 'Decay Constant', + max_history_length: 'Max Access History Length', + forgetting_threshold: 'Forgetting Threshold', + min_days_since_access: 'Minimum Days Since Access', + enable_llm_summary: 'Enable LLM Summary Generation', + max_merge_batch_size: 'Max Merge Batch Size', + forgetting_interval_hours: 'Forgetting Interval Hours' }, application: { searchPlaceholder: 'Search for applications or clusters', @@ -878,8 +886,6 @@ export const en = { toolCalling: 'Tool Calling', toolCallingDesc: 'The main control agent calls sub agents as tools', toolCallingFeature: 'Centralized control, suitable for structured workflow', - handoffs: 'Handoffs', - handoffsDesc: 'Agents between dynamic transfer of control rights', handoffsFeature: 'Decentralized control, suitable for complex conversation scenarios', recommend: 'Recommend', advanced: 'Advanced', @@ -1000,6 +1006,21 @@ export const en = { promptChatPlaceholder: 'Describe the prompt you need, e.g.: I need a customer service assistant', promptChatEmpty: 'No conversation content available', promptEmpty: 'Describe your use case on the left, and the orchestration preview will be displayed here.', + + master: 'Supervisor Mode', + master_agent: 'Supervisor Mode', + master_agentDesc: 'Unified scheduling and management by the main Agent, with sub-Agents executing tasks assigned by the supervisor, suitable for scenarios requiring centralized control.', + handoffs: 'Collaboration Mode', + handoffsDesc: 'Multiple Agents collaborate equally, autonomously coordinating according to task requirements, suitable for complex scenarios requiring flexible interaction.', + masterConfig: 'Supervisor Configuration', + orchestrationMode: 'Task Assignment Strategy', + conditional: 'Intelligent Assignment', + sequential: 'Sequential Assignment', + parallel: 'Parallel Assignment', + aggregationStrategy: 'Result Aggregation Method', + merge: 'Complete Aggregation', + vote: 'Key Information Extraction', + priority: 'Structured Integration', }, userMemory: { userMemory: 'User Memory', @@ -1355,15 +1376,15 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re last_health_check: 'Last Connection', responseTime: 'Response Time', status: { - available: '可用', - unconfigured: '未配置', - configured_disabled: '已配置未启用', - error: '链接异常' + available: 'Available', + unconfigured: 'Unconfigured', + configured_disabled: 'Configured but Disabled', + error: 'Connection Error' }, - available_desc: 'API 已配置并启用', - unconfigured_desc: '需要配置 API Key', - configured_disabled_desc: 'API 已配置但未启用', - error_desc: 'API 已配置但链接异常', + available_desc: 'API is configured and enabled', + unconfigured_desc: 'Need to configure API Key', + configured_disabled_desc: 'API is configured but not enabled', + error_desc: 'API is configured but connection error', serviceEndpoint: 'Service Endpoint URL', serviceEndpointPlaceholder: 'URL of the service endpoint', @@ -1537,10 +1558,10 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re parallel: 'Parallel Execution', 'var-aggregator': 'Variable Aggregator', externalInteraction: 'External Interaction', - http_request: 'HTTP Request', + "http-request": 'HTTP Request', tools: 'Tools', code_execution: 'Code Execution', - template_rendering: 'Template Rendering', + "jinja-render": 'Template Rendering', cognitiveUpgrading: 'Cognitive Upgrading (Innovation)', task_planning: 'Task Planning', reasoning_control: 'Reasoning Control', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 1671548e..363c54c9 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -511,7 +511,7 @@ export const zh = { master: '主管模式', master_agent: '主管模式', - masterDesc: '由主 Agent 统一调度和管理,子 Agent 按照主管分配的任务执行,适合需要集中控制的场景。', + master_agentDesc: '由主 Agent 统一调度和管理,子 Agent 按照主管分配的任务执行,适合需要集中控制的场景。', handoffs: '协作模式', handoffsDesc: '多个 Agent 平等协作,根据任务需求自主协调配合,适合需要灵活互动的复杂场景。', masterConfig: '主管配置', @@ -1091,6 +1091,14 @@ export const zh = { quicklyForget: '快速遗忘', slowForgetting: '缓慢遗忘', currentConfig: '当前配置', + + decay_constant: '衰减常数', + max_history_length: '访问历史最大长度', + forgetting_threshold: '遗忘阈值', + min_days_since_access: '最小未访问天数', + enable_llm_summary: '是否使用 LLM 生成摘要', + max_merge_batch_size: '单次最大融合节点对数', + forgetting_interval_hours: '遗忘周期间隔' }, userMemory: { userMemory: '用户记忆', @@ -1651,10 +1659,10 @@ export const zh = { parallel: '并行执行', 'var-aggregator': '变量聚合器', externalInteraction: '外部交互', - http_request: 'HTTP请求', + "http-request": 'HTTP请求', tools: '工具 (Tools)', code_execution: '代码执行', - template_rendering: '模板渲染', + "jinja-render": '模板渲染', cognitiveUpgrading: '认知升级(创新)', task_planning: '任务规划', reasoning_control: '推理控制', @@ -1756,7 +1764,37 @@ export const zh = { "gt": '>', "ge": '>=', else_desc: '用于定义当 if 条件不满足时应执行的逻辑。' - } + }, + 'http-request': { + auth: '鉴权', + authType: '鉴权类型', + apiKey: 'API Key', + basic: '基础', + bearer: 'Bearer', + custom: '自定义', + header: 'Header', + api_key: 'API Key', + timeouts: '超时设置', + "connect_timeout": '连接超时(秒)', + "read_timeout": '读取超时(秒)', + "write_timeout": '写入超时(秒)', + retry: '失败时重试', + error_handle: '异常处理', + verify_ssl: '验证 SSL 证书', + none: '无', + default: '默认值', + branch: '异常分支', + status_code: '状态码', + max_attempts: '最大重试次数', + retry_interval: '重试间隔', + }, + 'jinja-render': { + template: '代码', + mapping: '输入变量' + }, + name: '键', + type: '类型', + value: '值', }, clear: '清空', diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 436cd74f..c6aa63e8 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -17,7 +17,8 @@ import type { KnowledgeConfig, Variable, MemoryConfig, - AiPromptModalRef + AiPromptModalRef, + Source } from './types' import type { Model } from '@/views/ModelManagement/types' import { getModelList } from '@/api/models'; @@ -200,7 +201,7 @@ const Agent = forwardRef((_props, ref) => { }) } - const refresh = (vo: ModelConfig, type: 'model' | 'chat') => { + const refresh = (vo: ModelConfig, type: Source) => { if (type === 'model') { const { default_model_config_id, ...rest } = vo form.setFieldsValue({ @@ -445,7 +446,6 @@ const Agent = forwardRef((_props, ref) => { diff --git a/web/src/views/ApplicationConfig/Api.tsx b/web/src/views/ApplicationConfig/Api.tsx index e9fdea68..02c066e4 100644 --- a/web/src/views/ApplicationConfig/Api.tsx +++ b/web/src/views/ApplicationConfig/Api.tsx @@ -77,6 +77,7 @@ const Api: FC<{ application: Application | null }> = ({ application }) => { title: t('common.confirmDeleteDesc', { name: vo.name }), content: t('application.apiKeyDeleteContent'), okText: t('common.delete'), + cancelText: t('common.cancel'), okType: 'danger', onOk: () => { deleteApiKey(vo.id) diff --git a/web/src/views/ApplicationConfig/Cluster.tsx b/web/src/views/ApplicationConfig/Cluster.tsx index 4330cd60..058c3442 100644 --- a/web/src/views/ApplicationConfig/Cluster.tsx +++ b/web/src/views/ApplicationConfig/Cluster.tsx @@ -1,29 +1,31 @@ -import { type FC, useEffect, useState, useRef, forwardRef, useImperativeHandle, type Key } from 'react' +import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react' import { useTranslation } from 'react-i18next' import { useParams } from 'react-router-dom'; import Card from './components/Card' -import { Form, Space, Row, Col, Button, Flex, App } from 'antd' -import type { DefaultOptionType } from 'antd/es/select' +import { Form, Space, Row, Col, Button, Flex, App, Select } from 'antd' import Tag, { type TagProps } from './components/Tag' import CustomSelect from '@/components/CustomSelect'; -import { getApplicationListUrl, getMultiAgentConfig, saveMultiAgentConfig } from '@/api/application'; +import { getMultiAgentConfig, saveMultiAgentConfig } from '@/api/application'; import type { Config, SubAgentModalRef, ChatData, SubAgentItem, - ClusterRef + ClusterRef, + ModelConfigModalRef } from './types' import Chat from './components/Chat' import RbCard from '@/components/RbCard/Card' import SubAgentModal from './components/SubAgentModal' import Empty from '@/components/Empty' -import type { Application } from '@/views/ApplicationManagement/types' +import RadioGroupCard from '@/components/RadioGroupCard' +import { getModelListUrl } from '@/api/models' +import ModelConfigModal from './components/ModelConfigModal' const tagColors = ['processing', 'warning', 'default'] const MAX_LENGTH = 5; -const Cluster = forwardRef(({application}, ref) => { +const Cluster = forwardRef((_props, ref) => { const { t } = useTranslation() const { message } = App.useApp() const [form] = Form.useForm() @@ -39,7 +41,15 @@ const Cluster = forwardRef(({applicati ]) const handleSave = (flag = true) => { + if (!data) return Promise.resolve() + if (!values.default_model_config_id) { + message.warning(t('common.selectPlaceholder', { title: t('application.model') })) + return Promise.resolve() + } + const params = { + id: data.id, + app_id: data.app_id, ...values, sub_agents: (subAgents || []).map(item => ({ ...item, @@ -47,6 +57,8 @@ const Cluster = forwardRef(({applicati })) } + console.log('params', params) + return new Promise((resolve, reject) => { form.validateFields().then(() => { saveMultiAgentConfig(id as string, params) @@ -60,21 +72,14 @@ const Cluster = forwardRef(({applicati reject(error) }) }) - .catch(error => { - reject(error) - }) + .catch(error => { + reject(error) + }) }) } useEffect(() => { getData() }, [id]) - useEffect(() => { - if (application) { - form.setFieldsValue({ - name: application.name, - }) - } - }, [application]) const getData = () => { if (!id) { @@ -93,7 +98,6 @@ const Cluster = forwardRef(({applicati subAgentModalRef.current?.handleOpen(agent) } const refreshSubAgents = (agent: SubAgentItem) => { - // setSubAgents(subAgents) const index = subAgents.findIndex(item => item.agent_id === agent.agent_id) const newSubAgents = [...subAgents] if (index === -1) { @@ -110,90 +114,131 @@ const Cluster = forwardRef(({applicati const handleDeleteSubAgent = (agent: SubAgentItem) => { setSubAgents(prev => prev.filter(item => item.agent_id !== agent.agent_id)) } - const handleChange = (value: Key, option?: DefaultOptionType | DefaultOptionType[] | undefined) => { - if (option && !Array.isArray(option)) { - form.setFieldsValue({ master_agent_name: option.children }) - } - } useImperativeHandle(ref, () => ({ handleSave })) + const modelConfigModalRef = useRef(null) + const handleEditModelConfig = () => { + modelConfigModalRef.current?.handleOpen('multi_agent', values.model_parameters) + } + const handleSaveModelConfig = (values: Config['model_parameters']) => { + form.setFieldsValue({ + model_parameters: values + }) + } + return ( -
+
- - - - - {t('application.agentName')} -
- } - className="rb:mb-[20px]!" - rules={[{ required: true, message: t('common.pleaseSelect') }]} - > - - -
+ + + ({ + value: type, + label: t(`application.${type}`), + labelDesc: t(`application.${type}Desc`), + }))} + allowClear={false} + /> + -
{t('application.added')}: {subAgents.length}/{MAX_LENGTH}
+
{t('application.added')}: {subAgents.length}/{MAX_LENGTH}
{subAgents.length === 0 ? : subAgents.map((agent, index) => ( - -
+
{agent.name?.[0]}
{agent.name} - {agent.role &&
{agent.role || '-'}
} - {agent.capabilities && {agent.capabilities.map((tag, tagIndex) => {tag})}} + {agent.role &&
{agent.role || '-'}
} + {agent.capabilities && {agent.capabilities.map((tag, tagIndex) => {tag})}}
-
handleSubAgentModal(agent)} >
-
handleDeleteSubAgent(agent)} >
))} + + + + + + + + + + + + + + + + + + ({ + value: type, + label: t(`application.${type}`), + }))} + /> + + @@ -213,6 +258,11 @@ const Cluster = forwardRef(({applicati ref={subAgentModalRef} refresh={refreshSubAgents} /> + ) }) diff --git a/web/src/views/ApplicationConfig/components/Chat.tsx b/web/src/views/ApplicationConfig/components/Chat.tsx index ea35fa5a..bd826ba1 100644 --- a/web/src/views/ApplicationConfig/components/Chat.tsx +++ b/web/src/views/ApplicationConfig/components/Chat.tsx @@ -16,7 +16,7 @@ interface ChatProps { chatList: ChatData[]; data: Config; updateChatList: React.Dispatch>; - handleSave: (flag?: boolean) => Promise; + handleSave: (flag?: boolean) => Promise; source?: 'multi_agent' | 'agent'; } const Chat: FC = ({ chatList, data, updateChatList, handleSave, source = 'agent' }) => { @@ -74,7 +74,7 @@ const Chat: FC = ({ chatList, data, updateChatList, handleSave, sourc const curModelChat = modelChatList[targetIndex] const curChatMsgList = curModelChat.list || [] const lastMsg = curChatMsgList[curChatMsgList.length - 1] - if (lastMsg.role === 'assistant') { + if (lastMsg && lastMsg.role === 'assistant') { modelChatList[targetIndex] = { ...modelChatList[targetIndex], conversation_id: conversation_id, @@ -128,7 +128,7 @@ const Chat: FC = ({ chatList, data, updateChatList, handleSave, sourc .then(() => { const message = form.getFieldValue('message') if (!message?.trim()) return - + addUserMessage(message) form.setFieldsValue({ message: undefined }) addAssistantMessage() @@ -139,7 +139,7 @@ const Chat: FC = ({ chatList, data, updateChatList, handleSave, sourc data.map(item => { const { model_config_id, conversation_id, content, message_length } = item.data as { model_config_id: string; conversation_id: string; content: string; message_length: number }; - switch(item.event) { + switch (item.event) { case 'model_message': updateAssistantMessage(content, model_config_id, conversation_id) break; diff --git a/web/src/views/ApplicationConfig/types.ts b/web/src/views/ApplicationConfig/types.ts index 894071e6..abe7a008 100644 --- a/web/src/views/ApplicationConfig/types.ts +++ b/web/src/views/ApplicationConfig/types.ts @@ -93,18 +93,15 @@ export interface Config extends MultiAgentConfig { export interface MultiAgentConfig { id: string; app_id: string; - // system_prompt: string; - // default_model_config_id?: string; - // model_parameters: ModelConfig; - // knowledge_retrieval: KnowledgeConfig | null; - // memory?: MemoryConfig; - // variables: Variable[]; - // tools: Record; - // is_active: boolean; - // created_at: number; - // updated_at: number; - master_agent_id?: string; + default_model_config_id?: string; + model_parameters: ModelConfig; + orchestration_mode: 'conditional' | 'sequential' | 'parallel'; sub_agents?: SubAgentItem[]; + routing_rules: null; + execution_config: { + routing_mode: 'master' | 'handoffs' + }; + aggregation_strategy: 'merge' | 'vote' | 'priority' } // 创建表单数据类型 @@ -116,21 +113,22 @@ export interface ApplicationModalData { // 定义组件暴露的方法接口 export interface AgentRef { - handleSave: (flag?: boolean) => Promise; + handleSave: (flag?: boolean) => Promise; } export interface ClusterRef { - handleSave: (flag?: boolean) => Promise; + handleSave: (flag?: boolean) => Promise; } export interface WorkflowRef { - handleSave: (flag?: boolean) => Promise; + handleSave: (flag?: boolean) => Promise; handleRun: () => void; graphRef: GraphRef } export interface ApplicationModalRef { handleOpen: (application?: Config) => void; } +export type Source = 'chat' | 'model' | 'multi_agent' export interface ModelConfigModalRef { - handleOpen: (source: 'chat' | 'model') => void; + handleOpen: (source: Source, model?: any) => void; } export interface ModelConfigModalData { model: string;