feat(web): cluster
This commit is contained in:
@@ -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<AgentRef>((_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<AgentRef>((_props, ref) => {
|
||||
<ModelConfigModal
|
||||
modelList={modelList}
|
||||
data={formData as Config}
|
||||
chatList={chatList}
|
||||
ref={modelConfigModalRef}
|
||||
refresh={refresh}
|
||||
/>
|
||||
|
||||
@@ -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)
|
||||
|
||||
@@ -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<ClusterRef, { application: Application }>(({application}, ref) => {
|
||||
const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
||||
const { t } = useTranslation()
|
||||
const { message } = App.useApp()
|
||||
const [form] = Form.useForm()
|
||||
@@ -39,7 +41,15 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({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<ClusterRef, { application: Application }>(({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<ClusterRef, { application: Application }>(({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<ClusterRef, { application: Application }>(({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<ClusterRef, { application: Application }>(({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<ModelConfigModalRef>(null)
|
||||
const handleEditModelConfig = () => {
|
||||
modelConfigModalRef.current?.handleOpen('multi_agent', values.model_parameters)
|
||||
}
|
||||
const handleSaveModelConfig = (values: Config['model_parameters']) => {
|
||||
form.setFieldsValue({
|
||||
model_parameters: values
|
||||
})
|
||||
}
|
||||
|
||||
return (
|
||||
<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]">
|
||||
<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()}>
|
||||
{t('common.save')}
|
||||
</Button>
|
||||
</div>
|
||||
<Form form={form} layout="vertical">
|
||||
<Space size={20} direction="vertical" style={{width: '100%'}}>
|
||||
<Card title={t('application.supervisorAgent')}>
|
||||
<Row gutter={18}>
|
||||
<Col span={24}>
|
||||
<Form.Item
|
||||
name="master_agent_id"
|
||||
label={
|
||||
<div className="rb:font-medium">
|
||||
{t('application.agentName')}
|
||||
</div>
|
||||
}
|
||||
className="rb:mb-[20px]!"
|
||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||
>
|
||||
<CustomSelect
|
||||
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 title={t('application.handoffs')}>
|
||||
<Form.Item
|
||||
name={['execution_config', 'routing_mode']}
|
||||
noStyle
|
||||
>
|
||||
<RadioGroupCard
|
||||
options={['master_agent', 'handoffs'].map((type) => ({
|
||||
value: type,
|
||||
label: t(`application.${type}`),
|
||||
labelDesc: t(`application.${type}Desc`),
|
||||
}))}
|
||||
allowClear={false}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Card>
|
||||
|
||||
<Card title={t('application.subAgentsManagement')}>
|
||||
<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>
|
||||
</Flex>
|
||||
|
||||
{subAgents.length === 0
|
||||
? <Empty size={88} />
|
||||
: subAgents.map((agent, index) => (
|
||||
<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]!"
|
||||
<Flex key={index} align="center" justify="space-between"
|
||||
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)]!">
|
||||
<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]}
|
||||
</div>
|
||||
<div className="rb:flex rb:flex-col rb:justify-center rb:max-w-[calc(100%-60px)]">
|
||||
{agent.name}
|
||||
{agent.role && <div className="rb:font-regular rb:leading-[20px] rb:text-[#5B6167] rb:mt-[6px]">{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.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-4">{agent.capabilities.map((tag, tagIndex) => <Tag key={tagIndex} color={tagColors[tagIndex % tagColors.length] as TagProps['color']}>{tag}</Tag>)}</Flex>}
|
||||
</div>
|
||||
</Flex>
|
||||
|
||||
<Space>
|
||||
<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')]"
|
||||
<div
|
||||
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)}
|
||||
></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')]"
|
||||
<div
|
||||
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)}
|
||||
></div>
|
||||
</Space>
|
||||
</Flex>
|
||||
))}
|
||||
</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>
|
||||
</Form>
|
||||
</Col>
|
||||
@@ -213,6 +258,11 @@ const Cluster = forwardRef<ClusterRef, { application: Application }>(({applicati
|
||||
ref={subAgentModalRef}
|
||||
refresh={refreshSubAgents}
|
||||
/>
|
||||
<ModelConfigModal
|
||||
data={values as Config}
|
||||
ref={modelConfigModalRef}
|
||||
refresh={handleSaveModelConfig}
|
||||
/>
|
||||
</Row>
|
||||
)
|
||||
})
|
||||
|
||||
@@ -16,7 +16,7 @@ interface ChatProps {
|
||||
chatList: ChatData[];
|
||||
data: Config;
|
||||
updateChatList: React.Dispatch<React.SetStateAction<ChatData[]>>;
|
||||
handleSave: (flag?: boolean) => Promise<any>;
|
||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||
source?: 'multi_agent' | '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 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<ChatProps> = ({ 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<ChatProps> = ({ 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;
|
||||
|
||||
@@ -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<string, string>;
|
||||
// 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<any>;
|
||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||
}
|
||||
export interface ClusterRef {
|
||||
handleSave: (flag?: boolean) => Promise<any>;
|
||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||
}
|
||||
export interface WorkflowRef {
|
||||
handleSave: (flag?: boolean) => Promise<any>;
|
||||
handleSave: (flag?: boolean) => Promise<unknown>;
|
||||
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;
|
||||
|
||||
Reference in New Issue
Block a user