Merge #99 into develop_web from feature/20251219_zy
feat(web): cluster * feature/20251219_zy: (4 commits) fix(web): remove icon fix(web): Editor fix(web): order history feat(web): cluster Signed-off-by: zhaoying <zhaoying@redbearai.com> Merged-by: zhaoying <zhaoying@redbearai.com> CR-link: https://codeup.aliyun.com/redbearai/python/redbear-mem-open/change/99
This commit is contained in:
@@ -752,6 +752,14 @@ export const en = {
|
|||||||
quicklyForget: 'Quickly forget',
|
quicklyForget: 'Quickly forget',
|
||||||
slowForgetting: 'Slow forgetting',
|
slowForgetting: 'Slow forgetting',
|
||||||
currentConfig: 'Current Config',
|
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: {
|
application: {
|
||||||
searchPlaceholder: 'Search for applications or clusters',
|
searchPlaceholder: 'Search for applications or clusters',
|
||||||
@@ -905,8 +913,6 @@ export const en = {
|
|||||||
toolCalling: 'Tool Calling',
|
toolCalling: 'Tool Calling',
|
||||||
toolCallingDesc: 'The main control agent calls sub agents as tools',
|
toolCallingDesc: 'The main control agent calls sub agents as tools',
|
||||||
toolCallingFeature: 'Centralized control, suitable for structured workflow',
|
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',
|
handoffsFeature: 'Decentralized control, suitable for complex conversation scenarios',
|
||||||
recommend: 'Recommend',
|
recommend: 'Recommend',
|
||||||
advanced: 'Advanced',
|
advanced: 'Advanced',
|
||||||
@@ -1027,6 +1033,21 @@ export const en = {
|
|||||||
promptChatPlaceholder: 'Describe the prompt you need, e.g.: I need a customer service assistant',
|
promptChatPlaceholder: 'Describe the prompt you need, e.g.: I need a customer service assistant',
|
||||||
promptChatEmpty: 'No conversation content available',
|
promptChatEmpty: 'No conversation content available',
|
||||||
promptEmpty: 'Describe your use case on the left, and the orchestration preview will be displayed here.',
|
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: {
|
||||||
userMemory: 'User Memory',
|
userMemory: 'User Memory',
|
||||||
@@ -1382,15 +1403,15 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
last_health_check: 'Last Connection',
|
last_health_check: 'Last Connection',
|
||||||
responseTime: 'Response Time',
|
responseTime: 'Response Time',
|
||||||
status: {
|
status: {
|
||||||
available: '可用',
|
available: 'Available',
|
||||||
unconfigured: '未配置',
|
unconfigured: 'Unconfigured',
|
||||||
configured_disabled: '已配置未启用',
|
configured_disabled: 'Configured but Disabled',
|
||||||
error: '链接异常'
|
error: 'Connection Error'
|
||||||
},
|
},
|
||||||
available_desc: 'API 已配置并启用',
|
available_desc: 'API is configured and enabled',
|
||||||
unconfigured_desc: '需要配置 API Key',
|
unconfigured_desc: 'Need to configure API Key',
|
||||||
configured_disabled_desc: 'API 已配置但未启用',
|
configured_disabled_desc: 'API is configured but not enabled',
|
||||||
error_desc: 'API 已配置但链接异常',
|
error_desc: 'API is configured but connection error',
|
||||||
|
|
||||||
serviceEndpoint: 'Service Endpoint URL',
|
serviceEndpoint: 'Service Endpoint URL',
|
||||||
serviceEndpointPlaceholder: 'URL of the service endpoint',
|
serviceEndpointPlaceholder: 'URL of the service endpoint',
|
||||||
@@ -1564,10 +1585,10 @@ Memory Bear: After the rebellion, regional warlordism intensified for several re
|
|||||||
parallel: 'Parallel Execution',
|
parallel: 'Parallel Execution',
|
||||||
'var-aggregator': 'Variable Aggregator',
|
'var-aggregator': 'Variable Aggregator',
|
||||||
externalInteraction: 'External Interaction',
|
externalInteraction: 'External Interaction',
|
||||||
http_request: 'HTTP Request',
|
"http-request": 'HTTP Request',
|
||||||
tools: 'Tools',
|
tools: 'Tools',
|
||||||
code_execution: 'Code Execution',
|
code_execution: 'Code Execution',
|
||||||
template_rendering: 'Template Rendering',
|
"jinja-render": 'Template Rendering',
|
||||||
cognitiveUpgrading: 'Cognitive Upgrading (Innovation)',
|
cognitiveUpgrading: 'Cognitive Upgrading (Innovation)',
|
||||||
task_planning: 'Task Planning',
|
task_planning: 'Task Planning',
|
||||||
reasoning_control: 'Reasoning Control',
|
reasoning_control: 'Reasoning Control',
|
||||||
|
|||||||
@@ -535,7 +535,7 @@ export const zh = {
|
|||||||
|
|
||||||
master: '主管模式',
|
master: '主管模式',
|
||||||
master_agent: '主管模式',
|
master_agent: '主管模式',
|
||||||
masterDesc: '由主 Agent 统一调度和管理,子 Agent 按照主管分配的任务执行,适合需要集中控制的场景。',
|
master_agentDesc: '由主 Agent 统一调度和管理,子 Agent 按照主管分配的任务执行,适合需要集中控制的场景。',
|
||||||
handoffs: '协作模式',
|
handoffs: '协作模式',
|
||||||
handoffsDesc: '多个 Agent 平等协作,根据任务需求自主协调配合,适合需要灵活互动的复杂场景。',
|
handoffsDesc: '多个 Agent 平等协作,根据任务需求自主协调配合,适合需要灵活互动的复杂场景。',
|
||||||
masterConfig: '主管配置',
|
masterConfig: '主管配置',
|
||||||
@@ -1117,6 +1117,14 @@ export const zh = {
|
|||||||
quicklyForget: '快速遗忘',
|
quicklyForget: '快速遗忘',
|
||||||
slowForgetting: '缓慢遗忘',
|
slowForgetting: '缓慢遗忘',
|
||||||
currentConfig: '当前配置',
|
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: {
|
||||||
userMemory: '用户记忆',
|
userMemory: '用户记忆',
|
||||||
@@ -1677,10 +1685,10 @@ export const zh = {
|
|||||||
parallel: '并行执行',
|
parallel: '并行执行',
|
||||||
'var-aggregator': '变量聚合器',
|
'var-aggregator': '变量聚合器',
|
||||||
externalInteraction: '外部交互',
|
externalInteraction: '外部交互',
|
||||||
http_request: 'HTTP请求',
|
"http-request": 'HTTP请求',
|
||||||
tools: '工具 (Tools)',
|
tools: '工具 (Tools)',
|
||||||
code_execution: '代码执行',
|
code_execution: '代码执行',
|
||||||
template_rendering: '模板渲染',
|
"jinja-render": '模板渲染',
|
||||||
cognitiveUpgrading: '认知升级(创新)',
|
cognitiveUpgrading: '认知升级(创新)',
|
||||||
task_planning: '任务规划',
|
task_planning: '任务规划',
|
||||||
reasoning_control: '推理控制',
|
reasoning_control: '推理控制',
|
||||||
@@ -1782,7 +1790,37 @@ export const zh = {
|
|||||||
"gt": '>',
|
"gt": '>',
|
||||||
"ge": '>=',
|
"ge": '>=',
|
||||||
else_desc: '用于定义当 if 条件不满足时应执行的逻辑。'
|
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: '清空',
|
clear: '清空',
|
||||||
|
|||||||
@@ -17,7 +17,8 @@ import type {
|
|||||||
KnowledgeConfig,
|
KnowledgeConfig,
|
||||||
Variable,
|
Variable,
|
||||||
MemoryConfig,
|
MemoryConfig,
|
||||||
AiPromptModalRef
|
AiPromptModalRef,
|
||||||
|
Source
|
||||||
} from './types'
|
} from './types'
|
||||||
import type { Model } from '@/views/ModelManagement/types'
|
import type { Model } from '@/views/ModelManagement/types'
|
||||||
import { getModelList } from '@/api/models';
|
import { getModelList } from '@/api/models';
|
||||||
@@ -200,7 +201,7 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
const refresh = (vo: ModelConfig, type: 'model' | 'chat') => {
|
const refresh = (vo: ModelConfig, type: Source) => {
|
||||||
if (type === 'model') {
|
if (type === 'model') {
|
||||||
const { default_model_config_id, ...rest } = vo
|
const { default_model_config_id, ...rest } = vo
|
||||||
form.setFieldsValue({
|
form.setFieldsValue({
|
||||||
@@ -445,7 +446,6 @@ const Agent = forwardRef<AgentRef>((_props, ref) => {
|
|||||||
<ModelConfigModal
|
<ModelConfigModal
|
||||||
modelList={modelList}
|
modelList={modelList}
|
||||||
data={formData as Config}
|
data={formData as Config}
|
||||||
chatList={chatList}
|
|
||||||
ref={modelConfigModalRef}
|
ref={modelConfigModalRef}
|
||||||
refresh={refresh}
|
refresh={refresh}
|
||||||
/>
|
/>
|
||||||
|
|||||||
@@ -77,6 +77,7 @@ const Api: FC<{ application: Application | null }> = ({ application }) => {
|
|||||||
title: t('common.confirmDeleteDesc', { name: vo.name }),
|
title: t('common.confirmDeleteDesc', { name: vo.name }),
|
||||||
content: t('application.apiKeyDeleteContent'),
|
content: t('application.apiKeyDeleteContent'),
|
||||||
okText: t('common.delete'),
|
okText: t('common.delete'),
|
||||||
|
cancelText: t('common.cancel'),
|
||||||
okType: 'danger',
|
okType: 'danger',
|
||||||
onOk: () => {
|
onOk: () => {
|
||||||
deleteApiKey(vo.id)
|
deleteApiKey(vo.id)
|
||||||
|
|||||||
@@ -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 { useTranslation } from 'react-i18next'
|
||||||
import { useParams } from 'react-router-dom';
|
import { useParams } from 'react-router-dom';
|
||||||
import Card from './components/Card'
|
import Card from './components/Card'
|
||||||
import { Form, Space, Row, Col, Button, Flex, App } from 'antd'
|
import { Form, Space, Row, Col, Button, Flex, App, Select } from 'antd'
|
||||||
import type { DefaultOptionType } from 'antd/es/select'
|
|
||||||
import Tag, { type TagProps } from './components/Tag'
|
import Tag, { type TagProps } from './components/Tag'
|
||||||
import CustomSelect from '@/components/CustomSelect';
|
import CustomSelect from '@/components/CustomSelect';
|
||||||
import { getApplicationListUrl, getMultiAgentConfig, saveMultiAgentConfig } from '@/api/application';
|
import { getMultiAgentConfig, saveMultiAgentConfig } from '@/api/application';
|
||||||
import type {
|
import type {
|
||||||
Config,
|
Config,
|
||||||
SubAgentModalRef,
|
SubAgentModalRef,
|
||||||
ChatData,
|
ChatData,
|
||||||
SubAgentItem,
|
SubAgentItem,
|
||||||
ClusterRef
|
ClusterRef,
|
||||||
|
ModelConfigModalRef
|
||||||
} from './types'
|
} from './types'
|
||||||
import Chat from './components/Chat'
|
import Chat from './components/Chat'
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import SubAgentModal from './components/SubAgentModal'
|
import SubAgentModal from './components/SubAgentModal'
|
||||||
import Empty from '@/components/Empty'
|
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 tagColors = ['processing', 'warning', 'default']
|
||||||
const MAX_LENGTH = 5;
|
const MAX_LENGTH = 5;
|
||||||
const Cluster = forwardRef<ClusterRef, { application: Application }>(({application}, ref) => {
|
const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const { message } = App.useApp()
|
const { message } = App.useApp()
|
||||||
const [form] = Form.useForm()
|
const [form] = Form.useForm()
|
||||||
@@ -39,7 +41,15 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({applicati
|
|||||||
])
|
])
|
||||||
|
|
||||||
const handleSave = (flag = true) => {
|
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 = {
|
const params = {
|
||||||
|
id: data.id,
|
||||||
|
app_id: data.app_id,
|
||||||
...values,
|
...values,
|
||||||
sub_agents: (subAgents || []).map(item => ({
|
sub_agents: (subAgents || []).map(item => ({
|
||||||
...item,
|
...item,
|
||||||
@@ -47,6 +57,8 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({applicati
|
|||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
console.log('params', params)
|
||||||
|
|
||||||
return new Promise((resolve, reject) => {
|
return new Promise((resolve, reject) => {
|
||||||
form.validateFields().then(() => {
|
form.validateFields().then(() => {
|
||||||
saveMultiAgentConfig(id as string, params)
|
saveMultiAgentConfig(id as string, params)
|
||||||
@@ -60,21 +72,14 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({applicati
|
|||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
.catch(error => {
|
.catch(error => {
|
||||||
reject(error)
|
reject(error)
|
||||||
})
|
})
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
getData()
|
getData()
|
||||||
}, [id])
|
}, [id])
|
||||||
useEffect(() => {
|
|
||||||
if (application) {
|
|
||||||
form.setFieldsValue({
|
|
||||||
name: application.name,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
}, [application])
|
|
||||||
|
|
||||||
const getData = () => {
|
const getData = () => {
|
||||||
if (!id) {
|
if (!id) {
|
||||||
@@ -93,7 +98,6 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({applicati
|
|||||||
subAgentModalRef.current?.handleOpen(agent)
|
subAgentModalRef.current?.handleOpen(agent)
|
||||||
}
|
}
|
||||||
const refreshSubAgents = (agent: SubAgentItem) => {
|
const refreshSubAgents = (agent: SubAgentItem) => {
|
||||||
// setSubAgents(subAgents)
|
|
||||||
const index = subAgents.findIndex(item => item.agent_id === agent.agent_id)
|
const index = subAgents.findIndex(item => item.agent_id === agent.agent_id)
|
||||||
const newSubAgents = [...subAgents]
|
const newSubAgents = [...subAgents]
|
||||||
if (index === -1) {
|
if (index === -1) {
|
||||||
@@ -110,90 +114,131 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({applicati
|
|||||||
const handleDeleteSubAgent = (agent: SubAgentItem) => {
|
const handleDeleteSubAgent = (agent: SubAgentItem) => {
|
||||||
setSubAgents(prev => prev.filter(item => item.agent_id !== agent.agent_id))
|
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, () => ({
|
useImperativeHandle(ref, () => ({
|
||||||
handleSave
|
handleSave
|
||||||
}))
|
}))
|
||||||
|
|
||||||
|
const modelConfigModalRef = useRef<ModelConfigModalRef>(null)
|
||||||
|
const handleEditModelConfig = () => {
|
||||||
|
modelConfigModalRef.current?.handleOpen('multi_agent', values.model_parameters)
|
||||||
|
}
|
||||||
|
const handleSaveModelConfig = (values: Config['model_parameters']) => {
|
||||||
|
form.setFieldsValue({
|
||||||
|
model_parameters: values
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Row className="rb:h-[calc(100vh-64px)]">
|
<Row className="rb:h-[calc(100vh-64px)]">
|
||||||
<Col span={12} className="rb:h-full rb:overflow-x-auto rb:border-r rb:border-[#DFE4ED] rb:p-[20px_16px_24px_16px]">
|
<Col span={12} className="rb:h-full rb:overflow-x-auto rb:border-r rb:border-[#DFE4ED] rb:p-[20px_16px_24px_16px]">
|
||||||
<div className="rb:flex rb:items-center rb:justify-end rb:mb-[20px]">
|
<div className="rb:flex rb:items-center rb:justify-end rb:mb-5">
|
||||||
<Button type="primary" onClick={() => handleSave()}>
|
<Button type="primary" onClick={() => handleSave()}>
|
||||||
{t('common.save')}
|
{t('common.save')}
|
||||||
</Button>
|
</Button>
|
||||||
</div>
|
</div>
|
||||||
<Form form={form} layout="vertical">
|
<Form form={form} layout="vertical">
|
||||||
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
||||||
<Card title={t('application.supervisorAgent')}>
|
<Card title={t('application.handoffs')}>
|
||||||
<Row gutter={18}>
|
<Form.Item
|
||||||
<Col span={24}>
|
name={['execution_config', 'routing_mode']}
|
||||||
<Form.Item
|
noStyle
|
||||||
name="master_agent_id"
|
>
|
||||||
label={
|
<RadioGroupCard
|
||||||
<div className="rb:font-medium">
|
options={['master_agent', 'handoffs'].map((type) => ({
|
||||||
{t('application.agentName')}
|
value: type,
|
||||||
</div>
|
label: t(`application.${type}`),
|
||||||
}
|
labelDesc: t(`application.${type}Desc`),
|
||||||
className="rb:mb-[20px]!"
|
}))}
|
||||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
allowClear={false}
|
||||||
>
|
/>
|
||||||
<CustomSelect
|
</Form.Item>
|
||||||
url={getApplicationListUrl}
|
|
||||||
params={{ pagesize: 100, status: 'active', type: 'agent' }}
|
|
||||||
valueKey="id"
|
|
||||||
labelKey="name"
|
|
||||||
hasAll={false}
|
|
||||||
optionFilterProp="search"
|
|
||||||
showSearch={true}
|
|
||||||
onChange={handleChange}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
<Form.Item name="master_agent_name" hidden />
|
|
||||||
</Col>
|
|
||||||
</Row>
|
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
<Card title={t('application.subAgentsManagement')}>
|
<Card title={t('application.subAgentsManagement')}>
|
||||||
<Flex align="center" justify="space-between">
|
<Flex align="center" justify="space-between">
|
||||||
<div className="rb:font-regular rb:text-[#5B6167] rb:leading-[20px]">{t('application.added')}: {subAgents.length}/{MAX_LENGTH}</div>
|
<div className="rb:font-regular rb:text-[#5B6167] rb:leading-5">{t('application.added')}: {subAgents.length}/{MAX_LENGTH}</div>
|
||||||
<Button size="small" disabled={subAgents.length >= MAX_LENGTH} onClick={() => handleSubAgentModal()}>{t('application.addSubAgent')}</Button>
|
<Button size="small" disabled={subAgents.length >= MAX_LENGTH} onClick={() => handleSubAgentModal()}>{t('application.addSubAgent')}</Button>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
{subAgents.length === 0
|
{subAgents.length === 0
|
||||||
? <Empty size={88} />
|
? <Empty size={88} />
|
||||||
: subAgents.map((agent, index) => (
|
: subAgents.map((agent, index) => (
|
||||||
<Flex key={index} align="center" justify="space-between"
|
<Flex key={index} align="center" justify="space-between"
|
||||||
className="rb:mt-[16px]! rb:w-full! rb:border rb:border-[#DFE4ED] rb:rounded-[8px] rb:p-[20px_31px_20px_20px]!"
|
className="rb:mt-4! rb:w-full! rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:p-[20px_31px_20px_20px]!"
|
||||||
>
|
>
|
||||||
<Flex className="rb:w-[calc(100%-80px)]!">
|
<Flex className="rb:w-[calc(100%-80px)]!">
|
||||||
<div className="rb:w-[48px] rb:h-[48px] rb:rounded-[8px] rb:mr-[13px] rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
<div className="rb:w-12 rb:h-12 rb:rounded-lg rb:mr-3.25 rb:bg-[#155eef] rb:flex rb:items-center rb:justify-center rb:text-[28px] rb:text-[#ffffff]">
|
||||||
{agent.name?.[0]}
|
{agent.name?.[0]}
|
||||||
</div>
|
</div>
|
||||||
<div className="rb:flex rb:flex-col rb:justify-center rb:max-w-[calc(100%-60px)]">
|
<div className="rb:flex rb:flex-col rb:justify-center rb:max-w-[calc(100%-60px)]">
|
||||||
{agent.name}
|
{agent.name}
|
||||||
{agent.role && <div className="rb:font-regular rb:leading-[20px] rb:text-[#5B6167] rb:mt-[6px]">{agent.role || '-'}</div>}
|
{agent.role && <div className="rb:font-regular rb:leading-5 rb:text-[#5B6167] rb:mt-1.5">{agent.role || '-'}</div>}
|
||||||
{agent.capabilities && <Flex wrap gap={8} className="rb:mt-[16px]">{agent.capabilities.map((tag, tagIndex) => <Tag key={tagIndex} color={tagColors[tagIndex % tagColors.length] as TagProps['color']}>{tag}</Tag>)}</Flex>}
|
{agent.capabilities && <Flex wrap gap={8} className="rb:mt-4">{agent.capabilities.map((tag, tagIndex) => <Tag key={tagIndex} color={tagColors[tagIndex % tagColors.length] as TagProps['color']}>{tag}</Tag>)}</Flex>}
|
||||||
</div>
|
</div>
|
||||||
</Flex>
|
</Flex>
|
||||||
|
|
||||||
<Space>
|
<Space>
|
||||||
<div
|
<div
|
||||||
className="rb:w-[32px] rb:h-[32px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
className="rb:w-8 rb:h-8 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/editBorder.svg')] rb:hover:bg-[url('@/assets/images/editBg.svg')]"
|
||||||
onClick={() => handleSubAgentModal(agent)}
|
onClick={() => handleSubAgentModal(agent)}
|
||||||
></div>
|
></div>
|
||||||
<div
|
<div
|
||||||
className="rb:w-[32px] rb:h-[32px] rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
className="rb:w-8 rb:h-8 rb:cursor-pointer rb:bg-cover rb:bg-[url('@/assets/images/deleteBorder.svg')] rb:hover:bg-[url('@/assets/images/deleteBg.svg')]"
|
||||||
onClick={() => handleDeleteSubAgent(agent)}
|
onClick={() => handleDeleteSubAgent(agent)}
|
||||||
></div>
|
></div>
|
||||||
</Space>
|
</Space>
|
||||||
</Flex>
|
</Flex>
|
||||||
))}
|
))}
|
||||||
</Card>
|
</Card>
|
||||||
|
|
||||||
|
<Card title={t('application.masterConfig')}>
|
||||||
|
<Form.Item
|
||||||
|
label={t('application.model')}
|
||||||
|
required={true}
|
||||||
|
>
|
||||||
|
<Row gutter={16}>
|
||||||
|
<Col span={16}>
|
||||||
|
<Form.Item name="default_model_config_id" noStyle>
|
||||||
|
<CustomSelect
|
||||||
|
url={getModelListUrl}
|
||||||
|
params={{ type: 'llm,chat', pagesize: 100 }}
|
||||||
|
valueKey="id"
|
||||||
|
labelKey="name"
|
||||||
|
hasAll={false}
|
||||||
|
style={{ width: '100%' }}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
<Col span={8}>
|
||||||
|
<Form.Item name="model_parameters" noStyle>
|
||||||
|
<Button onClick={handleEditModelConfig}>{t('application.modelConfig')}</Button>
|
||||||
|
</Form.Item>
|
||||||
|
</Col>
|
||||||
|
</Row>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="orchestration_mode"
|
||||||
|
label={t('application.orchestrationMode')}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={['conditional', 'sequential', 'parallel'].map((type) => ({
|
||||||
|
value: type,
|
||||||
|
label: t(`application.${type}`),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
<Form.Item
|
||||||
|
name="aggregation_strategy"
|
||||||
|
label={t('application.aggregationStrategy')}
|
||||||
|
>
|
||||||
|
<Select
|
||||||
|
options={['merge', 'vote', 'priority'].map((type) => ({
|
||||||
|
value: type,
|
||||||
|
label: t(`application.${type}`),
|
||||||
|
}))}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
</Card>
|
||||||
</Space>
|
</Space>
|
||||||
</Form>
|
</Form>
|
||||||
</Col>
|
</Col>
|
||||||
@@ -213,6 +258,11 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({applicati
|
|||||||
ref={subAgentModalRef}
|
ref={subAgentModalRef}
|
||||||
refresh={refreshSubAgents}
|
refresh={refreshSubAgents}
|
||||||
/>
|
/>
|
||||||
|
<ModelConfigModal
|
||||||
|
data={values as Config}
|
||||||
|
ref={modelConfigModalRef}
|
||||||
|
refresh={handleSaveModelConfig}
|
||||||
|
/>
|
||||||
</Row>
|
</Row>
|
||||||
)
|
)
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -16,7 +16,7 @@ interface ChatProps {
|
|||||||
chatList: ChatData[];
|
chatList: ChatData[];
|
||||||
data: Config;
|
data: Config;
|
||||||
updateChatList: React.Dispatch<React.SetStateAction<ChatData[]>>;
|
updateChatList: React.Dispatch<React.SetStateAction<ChatData[]>>;
|
||||||
handleSave: (flag?: boolean) => Promise<any>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
source?: 'multi_agent' | 'agent';
|
source?: 'multi_agent' | 'agent';
|
||||||
}
|
}
|
||||||
const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, source = 'agent' }) => {
|
const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, source = 'agent' }) => {
|
||||||
@@ -74,7 +74,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
const curModelChat = modelChatList[targetIndex]
|
const curModelChat = modelChatList[targetIndex]
|
||||||
const curChatMsgList = curModelChat.list || []
|
const curChatMsgList = curModelChat.list || []
|
||||||
const lastMsg = curChatMsgList[curChatMsgList.length - 1]
|
const lastMsg = curChatMsgList[curChatMsgList.length - 1]
|
||||||
if (lastMsg.role === 'assistant') {
|
if (lastMsg && lastMsg.role === 'assistant') {
|
||||||
modelChatList[targetIndex] = {
|
modelChatList[targetIndex] = {
|
||||||
...modelChatList[targetIndex],
|
...modelChatList[targetIndex],
|
||||||
conversation_id: conversation_id,
|
conversation_id: conversation_id,
|
||||||
@@ -128,7 +128,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
.then(() => {
|
.then(() => {
|
||||||
const message = form.getFieldValue('message')
|
const message = form.getFieldValue('message')
|
||||||
if (!message?.trim()) return
|
if (!message?.trim()) return
|
||||||
|
|
||||||
addUserMessage(message)
|
addUserMessage(message)
|
||||||
form.setFieldsValue({ message: undefined })
|
form.setFieldsValue({ message: undefined })
|
||||||
addAssistantMessage()
|
addAssistantMessage()
|
||||||
@@ -139,7 +139,7 @@ const Chat: FC<ChatProps> = ({ chatList, data, updateChatList, handleSave, sourc
|
|||||||
data.map(item => {
|
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 };
|
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':
|
case 'model_message':
|
||||||
updateAssistantMessage(content, model_config_id, conversation_id)
|
updateAssistantMessage(content, model_config_id, conversation_id)
|
||||||
break;
|
break;
|
||||||
|
|||||||
@@ -93,18 +93,15 @@ export interface Config extends MultiAgentConfig {
|
|||||||
export interface MultiAgentConfig {
|
export interface MultiAgentConfig {
|
||||||
id: string;
|
id: string;
|
||||||
app_id: string;
|
app_id: string;
|
||||||
// system_prompt: string;
|
default_model_config_id?: string;
|
||||||
// default_model_config_id?: string;
|
model_parameters: ModelConfig;
|
||||||
// model_parameters: ModelConfig;
|
orchestration_mode: 'conditional' | 'sequential' | 'parallel';
|
||||||
// knowledge_retrieval: KnowledgeConfig | null;
|
|
||||||
// memory?: MemoryConfig;
|
|
||||||
// variables: Variable[];
|
|
||||||
// tools: Record<string, string>;
|
|
||||||
// is_active: boolean;
|
|
||||||
// created_at: number;
|
|
||||||
// updated_at: number;
|
|
||||||
master_agent_id?: string;
|
|
||||||
sub_agents?: SubAgentItem[];
|
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 {
|
export interface AgentRef {
|
||||||
handleSave: (flag?: boolean) => Promise<any>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
export interface ClusterRef {
|
export interface ClusterRef {
|
||||||
handleSave: (flag?: boolean) => Promise<any>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
}
|
}
|
||||||
export interface WorkflowRef {
|
export interface WorkflowRef {
|
||||||
handleSave: (flag?: boolean) => Promise<any>;
|
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||||
handleRun: () => void;
|
handleRun: () => void;
|
||||||
graphRef: GraphRef
|
graphRef: GraphRef
|
||||||
}
|
}
|
||||||
export interface ApplicationModalRef {
|
export interface ApplicationModalRef {
|
||||||
handleOpen: (application?: Config) => void;
|
handleOpen: (application?: Config) => void;
|
||||||
}
|
}
|
||||||
|
export type Source = 'chat' | 'model' | 'multi_agent'
|
||||||
export interface ModelConfigModalRef {
|
export interface ModelConfigModalRef {
|
||||||
handleOpen: (source: 'chat' | 'model') => void;
|
handleOpen: (source: Source, model?: any) => void;
|
||||||
}
|
}
|
||||||
export interface ModelConfigModalData {
|
export interface ModelConfigModalData {
|
||||||
model: string;
|
model: string;
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import { getOrderDetail } from '@/api/order'
|
|||||||
import { STATUS } from '../index';
|
import { STATUS } from '../index';
|
||||||
|
|
||||||
|
|
||||||
const OrderDetail = forwardRef<OrderDetailRef>((_props, ref) => {
|
const OrderDetail = forwardRef<OrderDetailRef, { getProductType: (type: string) => void; }>(({ getProductType }, ref) => {
|
||||||
const { t } = useTranslation();
|
const { t } = useTranslation();
|
||||||
const [visible, setVisible] = useState(false);
|
const [visible, setVisible] = useState(false);
|
||||||
const [data, setData] = useState({})
|
const [data, setData] = useState({})
|
||||||
@@ -38,7 +38,7 @@ const OrderDetail = forwardRef<OrderDetailRef>((_props, ref) => {
|
|||||||
: key === 'status' && value
|
: key === 'status' && value
|
||||||
? t(`pricing.${STATUS[value as keyof typeof STATUS].key}`)
|
? t(`pricing.${STATUS[value as keyof typeof STATUS].key}`)
|
||||||
: key === 'product_type' && value
|
: key === 'product_type' && value
|
||||||
? t(`pricing.${value.toLowerCase()}.type`)
|
? t(`pricing.${getProductType(value)}.type`)
|
||||||
: value
|
: value
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
|
|||||||
@@ -51,10 +51,10 @@ const OrderHistory: React.FC = () => {
|
|||||||
]
|
]
|
||||||
const productTypeOptions = [
|
const productTypeOptions = [
|
||||||
{ label: t('pricing.allType'), value: null },
|
{ label: t('pricing.allType'), value: null },
|
||||||
...PRICE_LIST.map(vo => ({
|
{ label: t('pricing.personal.type'), value: 'FREE' },
|
||||||
label: t(`pricing.${vo.type}.type`),
|
{ label: t('pricing.team.type'), value: 'TEAM' },
|
||||||
value: vo.type
|
{ label: t('pricing.biz.type'), value: 'ENTERPRISE' },
|
||||||
}))
|
{ label: t('pricing.commerce.type'), value: 'OEM' },
|
||||||
]
|
]
|
||||||
|
|
||||||
const handleView = (order: Order) => {
|
const handleView = (order: Order) => {
|
||||||
@@ -128,6 +128,16 @@ const OrderHistory: React.FC = () => {
|
|||||||
end_time
|
end_time
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
const getProductType = (type: string) => {
|
||||||
|
const typeMap: Record<string, string> = {
|
||||||
|
'FREE': 'personal',
|
||||||
|
'TEAM': 'team',
|
||||||
|
'ENTERPRISE': 'biz',
|
||||||
|
'OEM': 'commerce'
|
||||||
|
};
|
||||||
|
return typeMap[type] || 'ENTERPRISE';
|
||||||
|
};
|
||||||
// 表格列配置
|
// 表格列配置
|
||||||
const columns: ColumnsType = [
|
const columns: ColumnsType = [
|
||||||
{
|
{
|
||||||
@@ -140,7 +150,7 @@ const OrderHistory: React.FC = () => {
|
|||||||
title: t('pricing.product_type'),
|
title: t('pricing.product_type'),
|
||||||
dataIndex: 'product_type',
|
dataIndex: 'product_type',
|
||||||
key: 'product_type',
|
key: 'product_type',
|
||||||
render: (type) => t(`pricing.${type.toLowerCase()}.type`)
|
render: (type) => t(`pricing.${getProductType(type)}.type`)
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
title: t('pricing.payable_amount'),
|
title: t('pricing.payable_amount'),
|
||||||
@@ -219,7 +229,7 @@ const OrderHistory: React.FC = () => {
|
|||||||
isScroll={true}
|
isScroll={true}
|
||||||
/>
|
/>
|
||||||
|
|
||||||
<OrderDetail ref={orderDetailRef} />
|
<OrderDetail ref={orderDetailRef} getProductType={getProductType} />
|
||||||
</div>
|
</div>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
import { useEffect, useState, type FC } from 'react';
|
import { useEffect, useState, type FC } from 'react';
|
||||||
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
import { useLexicalComposerContext } from '@lexical/react/LexicalComposerContext';
|
||||||
import { $getRoot, $getSelection } from 'lexical';
|
import { $getSelection, $isRangeSelection } from 'lexical';
|
||||||
|
|
||||||
import { INSERT_VARIABLE_COMMAND } from '../commands';
|
import { INSERT_VARIABLE_COMMAND } from '../commands';
|
||||||
import type { NodeProperties } from '../../../types'
|
import type { NodeProperties } from '../../../types'
|
||||||
@@ -23,43 +23,55 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
|
|||||||
useEffect(() => {
|
useEffect(() => {
|
||||||
return editor.registerUpdateListener(({ editorState }) => {
|
return editor.registerUpdateListener(({ editorState }) => {
|
||||||
editorState.read(() => {
|
editorState.read(() => {
|
||||||
const root = $getRoot();
|
const selection = $getSelection();
|
||||||
const text = root.getTextContent();
|
|
||||||
const shouldShow = text.includes('/');
|
if (!selection || !$isRangeSelection(selection)) {
|
||||||
|
setShowSuggestions(false);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const anchorNode = selection.anchor.getNode();
|
||||||
|
const anchorOffset = selection.anchor.offset;
|
||||||
|
|
||||||
|
// Get the text content of the current node
|
||||||
|
const nodeText = anchorNode.getTextContent();
|
||||||
|
|
||||||
|
// Check if we have a '/' at the current position or after line break
|
||||||
|
const textBeforeCursor = nodeText.substring(0, anchorOffset);
|
||||||
|
const shouldShow = textBeforeCursor.endsWith('/') ||
|
||||||
|
(textBeforeCursor === '/' && anchorOffset === 1);
|
||||||
|
|
||||||
setShowSuggestions(shouldShow);
|
setShowSuggestions(shouldShow);
|
||||||
|
|
||||||
if (shouldShow) {
|
if (shouldShow) {
|
||||||
const selection = $getSelection();
|
const domSelection = window.getSelection();
|
||||||
if (selection) {
|
if (domSelection && domSelection.rangeCount > 0) {
|
||||||
const domSelection = window.getSelection();
|
const range = domSelection.getRangeAt(0);
|
||||||
if (domSelection && domSelection.rangeCount > 0) {
|
const rect = range.getBoundingClientRect();
|
||||||
const range = domSelection.getRangeAt(0);
|
|
||||||
const rect = range.getBoundingClientRect();
|
|
||||||
|
|
||||||
const popupWidth = 280;
|
const popupWidth = 280;
|
||||||
const popupHeight = 200;
|
const popupHeight = 200;
|
||||||
const viewportWidth = window.innerWidth;
|
const viewportWidth = window.innerWidth;
|
||||||
const viewportHeight = window.innerHeight;
|
const viewportHeight = window.innerHeight;
|
||||||
|
|
||||||
let left = rect.left;
|
let left = rect.left;
|
||||||
let top = rect.top - 10;
|
let top = rect.top - 10;
|
||||||
|
|
||||||
if (left + popupWidth > viewportWidth) {
|
if (left + popupWidth > viewportWidth) {
|
||||||
left = viewportWidth - popupWidth - 10;
|
left = viewportWidth - popupWidth - 10;
|
||||||
}
|
|
||||||
if (left < 10) {
|
|
||||||
left = 10;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (top - popupHeight < 10) {
|
|
||||||
top = rect.bottom + 10;
|
|
||||||
if (top + popupHeight > viewportHeight) {
|
|
||||||
top = viewportHeight - popupHeight - 10;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setPopupPosition({ top, left });
|
|
||||||
}
|
}
|
||||||
|
if (left < 10) {
|
||||||
|
left = 10;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (top - popupHeight < 10) {
|
||||||
|
top = rect.bottom + 10;
|
||||||
|
if (top + popupHeight > viewportHeight) {
|
||||||
|
top = viewportHeight - popupHeight - 10;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
setPopupPosition({ top, left });
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@@ -112,7 +124,7 @@ const AutocompletePlugin: FC<{ options: Suggestion[] }> = ({ options }) => {
|
|||||||
<div style={{ padding: '4px 12px', fontSize: '12px', color: '#999', fontWeight: 'bold' }}>
|
<div style={{ padding: '4px 12px', fontSize: '12px', color: '#999', fontWeight: 'bold' }}>
|
||||||
{nodeName}
|
{nodeName}
|
||||||
</div>
|
</div>
|
||||||
{nodeOptions.map((option, index) => {
|
{nodeOptions.map((option) => {
|
||||||
const globalIndex = Object.values(groupedSuggestions).flat().indexOf(option);
|
const globalIndex = Object.values(groupedSuggestions).flat().indexOf(option);
|
||||||
return (
|
return (
|
||||||
<div
|
<div
|
||||||
|
|||||||
@@ -4,10 +4,11 @@ import {
|
|||||||
$createParagraphNode,
|
$createParagraphNode,
|
||||||
$createTextNode,
|
$createTextNode,
|
||||||
$getRoot,
|
$getRoot,
|
||||||
|
$getSelection,
|
||||||
$setSelection,
|
$setSelection,
|
||||||
$createRangeSelection,
|
$createRangeSelection,
|
||||||
$isParagraphNode,
|
|
||||||
$isTextNode,
|
$isTextNode,
|
||||||
|
$isRangeSelection,
|
||||||
} from 'lexical';
|
} from 'lexical';
|
||||||
|
|
||||||
import { $createVariableNode } from '../nodes/VariableNode';
|
import { $createVariableNode } from '../nodes/VariableNode';
|
||||||
@@ -26,64 +27,45 @@ const CommandPlugin = () => {
|
|||||||
INSERT_VARIABLE_COMMAND,
|
INSERT_VARIABLE_COMMAND,
|
||||||
(payload: InsertVariableCommandPayload) => {
|
(payload: InsertVariableCommandPayload) => {
|
||||||
editor.update(() => {
|
editor.update(() => {
|
||||||
const root = $getRoot();
|
const selection = $getSelection();
|
||||||
const text = root.getTextContent();
|
if (!selection || !$isRangeSelection(selection)) return;
|
||||||
const lastSlashIndex = text.lastIndexOf('/');
|
|
||||||
|
|
||||||
// Find the paragraph and the position to insert
|
const anchorNode = selection.anchor.getNode();
|
||||||
const paragraph = root.getFirstChild();
|
const anchorOffset = selection.anchor.offset;
|
||||||
if (!paragraph || !$isParagraphNode(paragraph)) return;
|
|
||||||
|
|
||||||
const children = paragraph.getChildren();
|
if ($isTextNode(anchorNode)) {
|
||||||
let insertPosition = 0;
|
const nodeText = anchorNode.getTextContent();
|
||||||
let currentTextLength = 0;
|
const textBeforeCursor = nodeText.substring(0, anchorOffset);
|
||||||
|
const textAfterCursor = nodeText.substring(anchorOffset);
|
||||||
// Find where to insert the new tag
|
|
||||||
for (let i = 0; i < children.length; i++) {
|
|
||||||
const child = children[i];
|
|
||||||
const childText = child.getTextContent();
|
|
||||||
|
|
||||||
if (currentTextLength + childText.length > lastSlashIndex) {
|
// Find the last '/' position
|
||||||
// Split this text node if needed
|
const lastSlashIndex = textBeforeCursor.lastIndexOf('/');
|
||||||
if ($isTextNode(child)) {
|
|
||||||
const beforeSlash = childText.substring(0, lastSlashIndex - currentTextLength);
|
if (lastSlashIndex !== -1) {
|
||||||
const afterSlash = childText.substring(lastSlashIndex - currentTextLength + 1);
|
// Split the text: before slash, insert variable, after cursor
|
||||||
|
const beforeSlash = textBeforeCursor.substring(0, lastSlashIndex);
|
||||||
if (beforeSlash) {
|
|
||||||
child.setTextContent(beforeSlash);
|
// Update the current text node with text before slash
|
||||||
insertPosition = i + 1;
|
anchorNode.setTextContent(beforeSlash);
|
||||||
} else {
|
|
||||||
insertPosition = i;
|
// Create and insert the variable node
|
||||||
child.remove();
|
const tagNode = $createVariableNode(payload.data);
|
||||||
}
|
const spaceNode = $createTextNode(' ');
|
||||||
|
|
||||||
// Insert tag and space
|
anchorNode.insertAfter(tagNode);
|
||||||
const tagNode = $createVariableNode(payload.data);
|
tagNode.insertAfter(spaceNode);
|
||||||
const spaceNode = $createTextNode(' ');
|
|
||||||
|
// Add remaining text if any
|
||||||
if (insertPosition < paragraph.getChildrenSize()) {
|
if (textAfterCursor) {
|
||||||
paragraph.getChildAtIndex(insertPosition)?.insertBefore(tagNode);
|
spaceNode.insertAfter($createTextNode(textAfterCursor));
|
||||||
tagNode.insertAfter(spaceNode);
|
|
||||||
} else {
|
|
||||||
paragraph.append(tagNode);
|
|
||||||
paragraph.append(spaceNode);
|
|
||||||
}
|
|
||||||
|
|
||||||
if (afterSlash) {
|
|
||||||
spaceNode.insertAfter($createTextNode(afterSlash));
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set cursor after space
|
|
||||||
const selection = $createRangeSelection();
|
|
||||||
selection.anchor.set(spaceNode.getKey(), 1, 'text');
|
|
||||||
selection.focus.set(spaceNode.getKey(), 1, 'text');
|
|
||||||
$setSelection(selection);
|
|
||||||
}
|
}
|
||||||
break;
|
|
||||||
|
// Set cursor after space
|
||||||
|
const newSelection = $createRangeSelection();
|
||||||
|
newSelection.anchor.set(spaceNode.getKey(), 1, 'text');
|
||||||
|
newSelection.focus.set(spaceNode.getKey(), 1, 'text');
|
||||||
|
$setSelection(newSelection);
|
||||||
}
|
}
|
||||||
|
|
||||||
currentTextLength += childText.length;
|
|
||||||
insertPosition = i + 1;
|
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
return true;
|
return true;
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
import { type FC } from 'react';
|
import { type FC } from 'react';
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
import { Input, Form, Space, Button, Row, Col, Select, type FormListOperation } from 'antd';
|
import { Input, Form, Space, Button, Row, Col, Select, type FormListOperation } from 'antd';
|
||||||
import { MinusCircleOutlined, PlusOutlined } from '@ant-design/icons';
|
import { MinusCircleOutlined } from '@ant-design/icons';
|
||||||
import Editor from '../Editor'
|
import Editor from '../Editor'
|
||||||
import type { Suggestion } from '../Editor/plugin/AutocompletePlugin'
|
import type { Suggestion } from '../Editor/plugin/AutocompletePlugin'
|
||||||
|
|
||||||
@@ -48,7 +48,7 @@ const MessageEditor: FC<TextareaProps> = ({
|
|||||||
{(fields, { add, remove }) => (
|
{(fields, { add, remove }) => (
|
||||||
<Space size={12} direction="vertical" className="rb:w-full">
|
<Space size={12} direction="vertical" className="rb:w-full">
|
||||||
{fields.map(({ key, name, ...restField }) => {
|
{fields.map(({ key, name, ...restField }) => {
|
||||||
const currentRole = values[parentName]?.[key].role || 'USER'
|
const currentRole = (values[parentName]?.[key].role || 'USER').toUpperCase()
|
||||||
|
|
||||||
return (
|
return (
|
||||||
<Space key={key} size={12} direction="vertical" className="rb:w-full rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-2 rb:py-1.5 rb:bg-white">
|
<Space key={key} size={12} direction="vertical" className="rb:w-full rb:border rb:border-[#DFE4ED] rb:rounded-md rb:px-2 rb:py-1.5 rb:bg-white">
|
||||||
@@ -86,7 +86,7 @@ const MessageEditor: FC<TextareaProps> = ({
|
|||||||
)
|
)
|
||||||
})}
|
})}
|
||||||
<Form.Item>
|
<Form.Item>
|
||||||
<Button type="dashed" onClick={() => handleAdd(add)} block icon={<PlusOutlined />}>
|
<Button type="dashed" onClick={() => handleAdd(add)} block>
|
||||||
+{t('workflow.addMessage')}
|
+{t('workflow.addMessage')}
|
||||||
</Button>
|
</Button>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -237,6 +237,20 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
|
case 'knowledge-retrieval':
|
||||||
|
const knowledgeKey = `${nodeId}_message`;
|
||||||
|
if (!addedKeys.has(knowledgeKey)) {
|
||||||
|
addedKeys.add(knowledgeKey);
|
||||||
|
variableList.push({
|
||||||
|
key: knowledgeKey,
|
||||||
|
label: 'message',
|
||||||
|
type: 'variable',
|
||||||
|
dataType: 'String',
|
||||||
|
value: `${nodeId}.message`,
|
||||||
|
nodeData: nodeData,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
break
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
Reference in New Issue
Block a user