feat(web): model select component replace
This commit is contained in:
87
web/src/components/ModelSelect/index.tsx
Normal file
87
web/src/components/ModelSelect/index.tsx
Normal file
@@ -0,0 +1,87 @@
|
|||||||
|
/*
|
||||||
|
* @Author: ZhaoYing
|
||||||
|
* @Date: 2026-03-07 16:49:59
|
||||||
|
* @Last Modified by: ZhaoYing
|
||||||
|
* @Last Modified time: 2026-03-07 17:14:57
|
||||||
|
*/
|
||||||
|
import { useEffect, useState, type FC } from 'react';
|
||||||
|
import { Select, Flex, Space } from 'antd';
|
||||||
|
import type { SelectProps } from 'antd/es/select';
|
||||||
|
import { useTranslation } from 'react-i18next';
|
||||||
|
|
||||||
|
import { getModelList } from '@/api/models';
|
||||||
|
import type { Query, Model } from '@/views/ModelManagement/types';
|
||||||
|
import { getListLogoUrl } from '@/views/ModelManagement/utils';
|
||||||
|
import Tag from '@/components/Tag';
|
||||||
|
|
||||||
|
/** Extends AntD SelectProps; omits filterOption since it's handled internally */
|
||||||
|
interface ModelSelectProps extends SelectProps {
|
||||||
|
/** Extra query params passed to getModelList */
|
||||||
|
params?: Query;
|
||||||
|
placeholder?: string;
|
||||||
|
}
|
||||||
|
|
||||||
|
const ModelSelect: FC<ModelSelectProps> = ({
|
||||||
|
params,
|
||||||
|
placeholder,
|
||||||
|
...props
|
||||||
|
}) => {
|
||||||
|
const { t } = useTranslation();
|
||||||
|
const [options, setOptions] = useState<Model[]>([]);
|
||||||
|
|
||||||
|
// Fetch active models whenever params change; stringify for stable deep comparison
|
||||||
|
useEffect(() => {
|
||||||
|
getModelList({
|
||||||
|
...(params ?? {}),
|
||||||
|
pagesize: 100,
|
||||||
|
is_active: true
|
||||||
|
}).then((res) => {
|
||||||
|
setOptions((res as { items: Model[] }).items ?? []);
|
||||||
|
});
|
||||||
|
}, [JSON.stringify(params)]);
|
||||||
|
|
||||||
|
// Render the selected value inside the trigger with logo + truncated name
|
||||||
|
const labelRender: SelectProps['labelRender'] = ({ value }) => {
|
||||||
|
const item = options.find((o) => o.id === value);
|
||||||
|
if (!item) return undefined;
|
||||||
|
const logo = getListLogoUrl(item.provider, item.logo as string);
|
||||||
|
return (
|
||||||
|
<Flex align="center" gap={8}>
|
||||||
|
{logo && <img src={logo} className="rb:size-5 rb:rounded-md" alt="" />}
|
||||||
|
<div className="rb:flex-1 rb:text-ellipsis rb:overflow-hidden rb:whitespace-nowrap">{item.name}</div>
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Select
|
||||||
|
placeholder={placeholder ?? t('common.pleaseSelect')}
|
||||||
|
options={options}
|
||||||
|
fieldNames={{ label: 'name', value: 'id' }}
|
||||||
|
allowClear
|
||||||
|
popupMatchSelectWidth={false}
|
||||||
|
labelRender={labelRender}
|
||||||
|
// Each dropdown option shows logo, name, and capability tags
|
||||||
|
optionRender={(option) => {
|
||||||
|
const { data } = option;
|
||||||
|
const logo = getListLogoUrl(data.provider, data.logo as string);
|
||||||
|
return (
|
||||||
|
<Flex align="center" gap={8}>
|
||||||
|
<Flex align="center" gap={8}>
|
||||||
|
{logo && <img src={logo} className="rb:size-5 rb:rounded-md" alt="" />}
|
||||||
|
<span className="rb:wrap-break-word rb:line-clamp-1">{data.name as string}</span>
|
||||||
|
</Flex>
|
||||||
|
{data.capability?.length > 0 && (
|
||||||
|
<Space size={4}>
|
||||||
|
{data.capability.map((vo: string) => <Tag key={vo}>{vo}</Tag>)}
|
||||||
|
</Space>
|
||||||
|
)}
|
||||||
|
</Flex>
|
||||||
|
);
|
||||||
|
}}
|
||||||
|
{...props}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export default ModelSelect;
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:29:33
|
* @Date: 2026-02-03 16:29:33
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-04 10:20:16
|
* @Last Modified time: 2026-03-07 17:11:54
|
||||||
*/
|
*/
|
||||||
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react'
|
import { useEffect, useState, useRef, forwardRef, useImperativeHandle } from 'react'
|
||||||
import { useTranslation } from 'react-i18next'
|
import { useTranslation } from 'react-i18next'
|
||||||
@@ -11,7 +11,6 @@ import { Form, Space, Row, Col, Button, Flex, App, Select, Spin } from 'antd'
|
|||||||
|
|
||||||
import Card from './components/Card'
|
import Card from './components/Card'
|
||||||
import Tag from './components/Tag'
|
import Tag from './components/Tag'
|
||||||
import CustomSelect from '@/components/CustomSelect';
|
|
||||||
import { getMultiAgentConfig, saveMultiAgentConfig, getApplicationList } from '@/api/application';
|
import { getMultiAgentConfig, saveMultiAgentConfig, getApplicationList } from '@/api/application';
|
||||||
import type {
|
import type {
|
||||||
Config,
|
Config,
|
||||||
@@ -26,7 +25,7 @@ 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 RadioGroupCard from '@/components/RadioGroupCard'
|
import RadioGroupCard from '@/components/RadioGroupCard'
|
||||||
import { getModelListUrl } from '@/api/models'
|
import ModelSelect from '@/components/ModelSelect'
|
||||||
import ModelConfigModal from './components/ModelConfigModal'
|
import ModelConfigModal from './components/ModelConfigModal'
|
||||||
import type { Application } from '@/views/ApplicationManagement/types'
|
import type { Application } from '@/views/ApplicationManagement/types'
|
||||||
|
|
||||||
@@ -268,13 +267,9 @@ const Cluster = forwardRef<ClusterRef>((_props, ref) => {
|
|||||||
>
|
>
|
||||||
<Flex align="center" gap={12}>
|
<Flex align="center" gap={12}>
|
||||||
<Form.Item name="default_model_config_id" noStyle>
|
<Form.Item name="default_model_config_id" noStyle>
|
||||||
<CustomSelect
|
<ModelSelect
|
||||||
url={getModelListUrl}
|
params={{ type: 'llm,chat' }}
|
||||||
params={{ type: 'llm,chat', pagesize: 100, is_active: true }}
|
className="rb:w-full!"
|
||||||
valueKey="id"
|
|
||||||
labelKey="name"
|
|
||||||
hasAll={false}
|
|
||||||
style={{ width: '100%' }}
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
<Form.Item name="model_parameters" noStyle>
|
<Form.Item name="model_parameters" noStyle>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:26:44
|
* @Date: 2026-02-03 16:26:44
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-04 14:40:55
|
* @Last Modified time: 2026-03-07 17:12:25
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* AI Prompt Assistant Modal
|
* AI Prompt Assistant Modal
|
||||||
@@ -17,7 +17,6 @@ import clsx from 'clsx'
|
|||||||
import copy from 'copy-to-clipboard';
|
import copy from 'copy-to-clipboard';
|
||||||
|
|
||||||
import { updatePromptMessages, createPromptSessions } from '@/api/prompt'
|
import { updatePromptMessages, createPromptSessions } from '@/api/prompt'
|
||||||
import { getModelListUrl } from '@/api/models'
|
|
||||||
import type { AiPromptModalRef, AiPromptVariableModalRef, AiPromptForm } from '../types'
|
import type { AiPromptModalRef, AiPromptVariableModalRef, AiPromptForm } from '../types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import type { ModelListItem } from '@/views/ModelManagement/types'
|
import type { ModelListItem } from '@/views/ModelManagement/types'
|
||||||
@@ -25,11 +24,10 @@ import ChatContent from '@/components/Chat/ChatContent'
|
|||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
||||||
import type { ChatItem } from '@/components/Chat/types'
|
import type { ChatItem } from '@/components/Chat/types'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import ModelSelect from '@/components/ModelSelect'
|
||||||
import AiPromptVariableModal from './AiPromptVariableModal'
|
import AiPromptVariableModal from './AiPromptVariableModal'
|
||||||
import { type SSEMessage } from '@/utils/stream'
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
import Editor from './Editor'
|
import Editor from './Editor'
|
||||||
import { getLogoUrl } from '@/views/ModelManagement/utils'
|
|
||||||
import analysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png'
|
import analysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -215,23 +213,9 @@ const AiPromptModal = forwardRef<AiPromptModalRef, AiPromptModalProps>(({
|
|||||||
name="model_id"
|
name="model_id"
|
||||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||||
>
|
>
|
||||||
<CustomSelect
|
<ModelSelect
|
||||||
url={getModelListUrl}
|
params={{ type: 'llm,chat' }}
|
||||||
params={{ type: 'llm,chat', pagesize: 100, is_active: true }}
|
className="rb:w-full!"
|
||||||
hasAll={false}
|
|
||||||
optionLabelProp="children"
|
|
||||||
format={(data) => {
|
|
||||||
return data.map(option => ({
|
|
||||||
...data,
|
|
||||||
value: option.id,
|
|
||||||
label: (<div key={option.id} className="rb:flex rb:items-center rb:gap-2">
|
|
||||||
{getLogoUrl(option.logo as string) && <img src={getLogoUrl(option.logo as string)} className="rb:inline-block rb:size-4 rb:align-middle" alt="" />}
|
|
||||||
<span>{option.name as string}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
className="rb:w-full"
|
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:25:42
|
* @Date: 2026-02-03 16:25:42
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-24 11:47:32
|
* @Last Modified time: 2026-03-07 17:03:22
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Knowledge Global Configuration Modal
|
* Knowledge Global Configuration Modal
|
||||||
@@ -15,8 +15,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from './types'
|
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from './types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import ModelSelect from '@/components/ModelSelect'
|
||||||
import { getModelListUrl } from '@/api/models'
|
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
@@ -115,12 +114,9 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
|
|||||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||||
extra={t('application.rearrangementModelDesc')}
|
extra={t('application.rearrangementModelDesc')}
|
||||||
>
|
>
|
||||||
<CustomSelect
|
<ModelSelect
|
||||||
url={getModelListUrl}
|
params={{ type: 'rerank' }}
|
||||||
params={{ type: 'rerank', pagesize: 100, is_active: true }}
|
className="rb:w-full!"
|
||||||
valueKey="id"
|
|
||||||
labelKey="name"
|
|
||||||
hasAll={false}
|
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* Top K */}
|
{/* Top K */}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 16:50:18
|
* @Date: 2026-02-03 16:50:18
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-06 12:26:11
|
* @Last Modified time: 2026-03-07 16:14:25
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Type definitions for Model Management
|
* Type definitions for Model Management
|
||||||
@@ -115,6 +115,8 @@ export interface ModelApiKey {
|
|||||||
updated_at: number;
|
updated_at: number;
|
||||||
/** Associated model config IDs */
|
/** Associated model config IDs */
|
||||||
model_config_ids: string[];
|
model_config_ids: string[];
|
||||||
|
capability: Capability[];
|
||||||
|
is_omni?: boolean;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -325,3 +327,23 @@ export interface BaseRef {
|
|||||||
getList: () => void;
|
getList: () => void;
|
||||||
modelListDetailRefresh?: () => void;
|
modelListDetailRefresh?: () => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
export type Capability = 'vision' | 'audio' | 'video';
|
||||||
|
export interface Model {
|
||||||
|
name: string;
|
||||||
|
type: string;
|
||||||
|
logo: string;
|
||||||
|
description: string | null;
|
||||||
|
provider: string;
|
||||||
|
config: Record<string, unknown>;
|
||||||
|
is_active: boolean;
|
||||||
|
is_public: boolean;
|
||||||
|
load_balance_strategy: string;
|
||||||
|
capability: Capability[];
|
||||||
|
is_omni: boolean;
|
||||||
|
model_id: string | null;
|
||||||
|
id: string;
|
||||||
|
created_at: number;
|
||||||
|
updated_at: number;
|
||||||
|
api_keys: ModelApiKey[];
|
||||||
|
}
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 17:44:15
|
* @Date: 2026-02-03 17:44:15
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-02-10 16:35:47
|
* @Last Modified time: 2026-03-07 17:15:05
|
||||||
*/
|
*/
|
||||||
/**
|
/**
|
||||||
* Prompt Editor Component
|
* Prompt Editor Component
|
||||||
@@ -17,18 +17,16 @@ import copy from 'copy-to-clipboard';
|
|||||||
import { useNavigate, useLocation } from 'react-router-dom';
|
import { useNavigate, useLocation } from 'react-router-dom';
|
||||||
|
|
||||||
import { updatePromptMessages, createPromptSessions } from '@/api/prompt'
|
import { updatePromptMessages, createPromptSessions } from '@/api/prompt'
|
||||||
import { getModelListUrl } from '@/api/models'
|
|
||||||
import type { PromptVariableModalRef, AiPromptForm, HistoryItem, PromptSaveModalRef } from './types'
|
import type { PromptVariableModalRef, AiPromptForm, HistoryItem, PromptSaveModalRef } from './types'
|
||||||
import ChatContent from '@/components/Chat/ChatContent'
|
import ChatContent from '@/components/Chat/ChatContent'
|
||||||
import Empty from '@/components/Empty'
|
import Empty from '@/components/Empty'
|
||||||
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
import ConversationEmptyIcon from '@/assets/images/conversation/conversationEmpty.svg'
|
||||||
import type { ChatItem } from '@/components/Chat/types'
|
import type { ChatItem } from '@/components/Chat/types'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import ModelSelect from '@/components/ModelSelect'
|
||||||
import PromptVariableModal from './components/PromptVariableModal'
|
import PromptVariableModal from './components/PromptVariableModal'
|
||||||
import { type SSEMessage } from '@/utils/stream'
|
import { type SSEMessage } from '@/utils/stream'
|
||||||
import Editor from '@/views/ApplicationConfig/components/Editor'
|
import Editor from '@/views/ApplicationConfig/components/Editor'
|
||||||
import PromptSaveModal from './components/PromptSaveModal'
|
import PromptSaveModal from './components/PromptSaveModal'
|
||||||
import { getLogoUrl } from '@/views/ModelManagement/utils'
|
|
||||||
import analysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png'
|
import analysisEmptyIcon from '@/assets/images/conversation/analysisEmpty.png'
|
||||||
import Header from './components/Header';
|
import Header from './components/Header';
|
||||||
import RbCard from '@/components/RbCard/Card';
|
import RbCard from '@/components/RbCard/Card';
|
||||||
@@ -235,22 +233,8 @@ const Prompt: FC = () => {
|
|||||||
name="model_id"
|
name="model_id"
|
||||||
noStyle
|
noStyle
|
||||||
>
|
>
|
||||||
<CustomSelect
|
<ModelSelect
|
||||||
url={getModelListUrl}
|
params={{ type: 'llm,chat' }}
|
||||||
params={{ type: 'llm,chat', pagesize: 100, is_active: true }}
|
|
||||||
hasAll={false}
|
|
||||||
optionLabelProp="children"
|
|
||||||
format={(data) => {
|
|
||||||
return data.map(option => ({
|
|
||||||
...data,
|
|
||||||
value: option.id,
|
|
||||||
label: (<div key={option.id} className="rb:flex rb:items-center rb:gap-2">
|
|
||||||
{getLogoUrl(option.logo as string) && <img src={getLogoUrl(option.logo as string)} className="rb:inline-block rb:size-4 rb:align-middle" alt="" />}
|
|
||||||
<span>{option.name as string}</span>
|
|
||||||
</div>
|
|
||||||
)
|
|
||||||
}))
|
|
||||||
}}
|
|
||||||
className={`rb:w-75! ${styles.select}`}
|
className={`rb:w-75! ${styles.select}`}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
@@ -275,6 +259,7 @@ const Prompt: FC = () => {
|
|||||||
>{t('common.copy')}</Button>
|
>{t('common.copy')}</Button>
|
||||||
<Button
|
<Button
|
||||||
icon={<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/plus_dark.svg')]"></div>}
|
icon={<div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/plus_dark.svg')]"></div>}
|
||||||
|
disabled={!values?.current_prompt || loading}
|
||||||
onClick={handleAdd}
|
onClick={handleAdd}
|
||||||
></Button>
|
></Button>
|
||||||
</Space>}
|
</Space>}
|
||||||
@@ -286,7 +271,7 @@ const Prompt: FC = () => {
|
|||||||
className="rb:h-[calc(100vh-201px)] rb:bg-white! rb:border-none! rb:p-0! rb:text-[#212332] rb:leading-5"
|
className="rb:h-[calc(100vh-201px)] rb:bg-white! rb:border-none! rb:p-0! rb:text-[#212332] rb:leading-5"
|
||||||
onChange={(value) => form.setFieldValue('current_prompt', value)}
|
onChange={(value) => form.setFieldValue('current_prompt', value)}
|
||||||
/>
|
/>
|
||||||
: <Empty url={analysisEmptyIcon} title={t(`prompt.promptPlaceholder`)} isNeedSubTitle={false} size={[270, 170]} className="rb:h-119 rb:w-70 rb:mx-auto! rb:text-center! rb:text-[12px]! rb:leading-4!" />
|
: <Empty url={analysisEmptyIcon} title={t(`prompt.promptPlaceholder`)} isNeedSubTitle={false} size={[270, 170]} className="rb:h-[calc(100vh-201px)] rb:w-70 rb:mx-auto! rb:text-center! rb:text-[12px]! rb:leading-4!" />
|
||||||
}
|
}
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
</RbCard>
|
</RbCard>
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { useTranslation } from 'react-i18next';
|
|||||||
|
|
||||||
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from './types'
|
import type { RerankerConfig, KnowledgeGlobalConfigModalRef } from './types'
|
||||||
import RbModal from '@/components/RbModal'
|
import RbModal from '@/components/RbModal'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import ModelSelect from '@/components/ModelSelect'
|
||||||
import { getModelListUrl } from '@/api/models'
|
|
||||||
|
|
||||||
const FormItem = Form.Item;
|
const FormItem = Form.Item;
|
||||||
|
|
||||||
@@ -96,12 +95,9 @@ const KnowledgeGlobalConfigModal = forwardRef<KnowledgeGlobalConfigModalRef, Kno
|
|||||||
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
rules={[{ required: true, message: t('common.pleaseSelect') }]}
|
||||||
extra={t('application.rearrangementModelDesc')}
|
extra={t('application.rearrangementModelDesc')}
|
||||||
>
|
>
|
||||||
<CustomSelect
|
<ModelSelect
|
||||||
url={getModelListUrl}
|
params={{ type: 'rerank' }}
|
||||||
params={{ type: 'rerank', pagesize: 100, is_active: true }}
|
className="rb:w-full!"
|
||||||
valueKey="id"
|
|
||||||
labelKey="name"
|
|
||||||
hasAll={false}
|
|
||||||
/>
|
/>
|
||||||
</FormItem>
|
</FormItem>
|
||||||
{/* Top K */}
|
{/* Top K */}
|
||||||
|
|||||||
@@ -4,8 +4,7 @@ import { Form } from 'antd'
|
|||||||
|
|
||||||
import RbSlider from '@/components/RbSlider'
|
import RbSlider from '@/components/RbSlider'
|
||||||
import RbCard from '@/components/RbCard/Card'
|
import RbCard from '@/components/RbCard/Card'
|
||||||
import CustomSelect from '@/components/CustomSelect'
|
import ModelSelect from '@/components/ModelSelect'
|
||||||
import { getModelListUrl } from '@/api/models'
|
|
||||||
|
|
||||||
const ModelConfig: FC = () => {
|
const ModelConfig: FC = () => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
@@ -20,13 +19,10 @@ const ModelConfig: FC = () => {
|
|||||||
label={t('workflow.config.llm.model_id')}
|
label={t('workflow.config.llm.model_id')}
|
||||||
className={model_id ? 'rb:mb-2!' : 'rb:mb-4!'}
|
className={model_id ? 'rb:mb-2!' : 'rb:mb-4!'}
|
||||||
>
|
>
|
||||||
<CustomSelect
|
<ModelSelect
|
||||||
placeholder={t('common.pleaseSelect')}
|
placeholder={t('common.pleaseSelect')}
|
||||||
url={getModelListUrl}
|
params={{ type: 'llm,chat' }}
|
||||||
params={{ type: 'llm,chat', pagesize: 100, is_active: true }}
|
className="rb:w-full!"
|
||||||
hasAll={false}
|
|
||||||
valueKey="id"
|
|
||||||
labelKey="name"
|
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
</Form.Item>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 15:39:59
|
* @Date: 2026-02-03 15:39:59
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-05 17:48:25
|
* @Last Modified time: 2026-03-07 17:16:13
|
||||||
*/
|
*/
|
||||||
import { type FC, useEffect, useState, useMemo } from "react";
|
import { type FC, useEffect, useState, useMemo } from "react";
|
||||||
import clsx from 'clsx'
|
import clsx from 'clsx'
|
||||||
@@ -36,6 +36,7 @@ import CodeExecution from './CodeExecution'
|
|||||||
import { nodeLibrary } from '../../constant';
|
import { nodeLibrary } from '../../constant';
|
||||||
import RbCard from '@/components/RbCard/Card';
|
import RbCard from '@/components/RbCard/Card';
|
||||||
import ModelConfig from './ModelConfig'
|
import ModelConfig from './ModelConfig'
|
||||||
|
import ModelSelect from '@/components/ModelSelect'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Props for Properties component
|
* Props for Properties component
|
||||||
@@ -72,7 +73,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
}) => {
|
}) => {
|
||||||
const { t } = useTranslation()
|
const { t } = useTranslation()
|
||||||
const [form] = Form.useForm<NodeConfig>();
|
const [form] = Form.useForm<NodeConfig>();
|
||||||
const [configs, setConfigs] = useState<Record<string,NodeConfig>>({} as Record<string,NodeConfig>)
|
const [configs, setConfigs] = useState<Record<string, NodeConfig>>({} as Record<string, NodeConfig>)
|
||||||
const values = Form.useWatch([], form);
|
const values = Form.useWatch([], form);
|
||||||
const variableList = useVariableList(selectedNode, graphRef, chatVariables)
|
const variableList = useVariableList(selectedNode, graphRef, chatVariables)
|
||||||
|
|
||||||
@@ -416,7 +417,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
const handleClick: MenuProps['onClick'] = (e) => {
|
const handleClick: MenuProps['onClick'] = (e) => {
|
||||||
switch(e.key) {
|
switch (e.key) {
|
||||||
case 'delete':
|
case 'delete':
|
||||||
selectedNode.remove()
|
selectedNode.remove()
|
||||||
break;
|
break;
|
||||||
@@ -433,7 +434,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
<Dropdown
|
<Dropdown
|
||||||
menu={{
|
menu={{
|
||||||
items: [
|
items: [
|
||||||
{ key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex>},
|
{ key: 'delete', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('src/assets/images/common/delete_dark.svg')]"></div>, label: <Flex>{t('common.delete')}</Flex> },
|
||||||
// { key: 'copy', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></div>, label: t('common.copy') }
|
// { key: 'copy', icon: <div className="rb:size-4 rb:bg-cover rb:bg-[url('@/assets/images/common/copy_dark.svg')]"></div>, label: t('common.copy') }
|
||||||
],
|
],
|
||||||
onClick: handleClick
|
onClick: handleClick
|
||||||
@@ -482,341 +483,348 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
<Button type="primary" size="small" className="rb:text-[12px]!" onClick={handleSureReplace}>{t('workflow.sureReplace')}</Button>
|
<Button type="primary" size="small" className="rb:text-[12px]!" onClick={handleSureReplace}>{t('workflow.sureReplace')}</Button>
|
||||||
</>
|
</>
|
||||||
: selectedNode?.data?.type === 'http-request'
|
: selectedNode?.data?.type === 'http-request'
|
||||||
? <HttpRequest
|
? <HttpRequest
|
||||||
options={variableList}
|
options={variableList}
|
||||||
selectedNode={selectedNode}
|
selectedNode={selectedNode}
|
||||||
graphRef={graphRef}
|
graphRef={graphRef}
|
||||||
/>
|
/>
|
||||||
: selectedNode?.data?.type === 'tool'
|
: selectedNode?.data?.type === 'tool'
|
||||||
? <ToolConfig options={variableList} />
|
? <ToolConfig options={variableList} />
|
||||||
: selectedNode?.data?.type === 'jinja-render'
|
: selectedNode?.data?.type === 'jinja-render'
|
||||||
? <JinjaRender
|
? <JinjaRender
|
||||||
selectedNode={selectedNode}
|
selectedNode={selectedNode}
|
||||||
options={getFilteredVariableList(selectedNode?.data?.type, 'mapping')}
|
options={getFilteredVariableList(selectedNode?.data?.type, 'mapping')}
|
||||||
templateOptions={getFilteredVariableList(selectedNode?.data?.type, 'template')}
|
templateOptions={getFilteredVariableList(selectedNode?.data?.type, 'template')}
|
||||||
/>
|
/>
|
||||||
: selectedNode?.data?.type === 'code'
|
: selectedNode?.data?.type === 'code'
|
||||||
? <CodeExecution
|
? <CodeExecution
|
||||||
selectedNode={selectedNode}
|
|
||||||
options={getFilteredVariableList(selectedNode?.data?.type, 'mapping')}
|
|
||||||
/>
|
|
||||||
: configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => {
|
|
||||||
const config = configs[key] || {}
|
|
||||||
|
|
||||||
if (config.dependsOn && (values as any)?.[config.dependsOn as string] !== config.dependsOnValue) {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (selectedNode?.data?.type === 'start' && key === 'variables' && config.type === 'define') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key} className="rb:mb-0!">
|
|
||||||
<VariableList
|
|
||||||
parentName={key}
|
|
||||||
selectedNode={selectedNode}
|
selectedNode={selectedNode}
|
||||||
config={config}
|
options={getFilteredVariableList(selectedNode?.data?.type, 'mapping')}
|
||||||
/>
|
/>
|
||||||
</Form.Item>
|
: configs && Object.keys(configs).length > 0 && Object.keys(configs).map((key) => {
|
||||||
)
|
const config = configs[key] || {}
|
||||||
}
|
|
||||||
|
|
||||||
if (key === 'model_id' && selectedNode?.data?.type === 'llm') {
|
if (config.dependsOn && (values as any)?.[config.dependsOn as string] !== config.dependsOnValue) {
|
||||||
return <ModelConfig />
|
return null
|
||||||
}
|
|
||||||
if (selectedNode?.data?.type === 'llm' && key === 'messages' && config.type === 'define') {
|
|
||||||
// 为llm节点且isArray=true时添加context变量支持
|
|
||||||
let contextVariableList = [...getFilteredVariableList('llm')];
|
|
||||||
const isArrayMode = config.isArray !== false; // 默认为true
|
|
||||||
|
|
||||||
if (isArrayMode) {
|
|
||||||
const contextKey = `${selectedNode.id}_context`;
|
|
||||||
const hasContextVariable = contextVariableList.some(v => v.key === contextKey);
|
|
||||||
|
|
||||||
if (!hasContextVariable) {
|
|
||||||
contextVariableList.unshift({
|
|
||||||
key: contextKey,
|
|
||||||
label: 'context',
|
|
||||||
type: 'variable',
|
|
||||||
dataType: 'String',
|
|
||||||
value: `context`,
|
|
||||||
nodeData: selectedNode.getData(),
|
|
||||||
isContext: true,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<MessageEditor
|
|
||||||
key={key}
|
|
||||||
options={contextVariableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')}
|
|
||||||
parentName={key}
|
|
||||||
placeholder={t(config.placeholder || 'common.pleaseSelect')}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (selectedNode?.data?.type === 'iteration' && key === 'output_type') {
|
|
||||||
return (<Form.Item key={key} name={key} hidden />)
|
|
||||||
}
|
|
||||||
if (config.type === 'define') {
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'knowledge') {
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
key={key}
|
|
||||||
name={key}
|
|
||||||
>
|
|
||||||
<Knowledge />
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'messageEditor') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key} label={selectedNode?.data?.type === 'memory-write' ? t(`workflow.config.${selectedNode?.data?.type}.${key}`) : undefined}>
|
|
||||||
<MessageEditor
|
|
||||||
title={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
|
||||||
placeholder={t(config.placeholder || 'common.pleaseEnter')}
|
|
||||||
isArray={!!config.isArray}
|
|
||||||
parentName={key}
|
|
||||||
language={config.language as LexicalEditorProps['language']}
|
|
||||||
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
|
||||||
titleVariant={config.titleVariant}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
if (config.type === 'paramList') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<ParamsList
|
|
||||||
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'groupVariableList') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<GroupVariableList
|
|
||||||
name={key}
|
|
||||||
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
|
||||||
isCanAdd={!!(values as any)?.group}
|
|
||||||
size="small"
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'caseList') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key} noStyle>
|
|
||||||
<CaseList
|
|
||||||
name={key}
|
|
||||||
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
|
||||||
selectedNode={selectedNode}
|
|
||||||
graphRef={graphRef}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'cycleVarsList') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<CycleVarsList
|
|
||||||
size="small"
|
|
||||||
parentName={key}
|
|
||||||
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'assignmentList') {
|
|
||||||
return (
|
|
||||||
<Form.Item key={key} name={key}>
|
|
||||||
<AssignmentList
|
|
||||||
parentName={key}
|
|
||||||
options={(() => {
|
|
||||||
if (config.filterLoopIterationVars) {
|
|
||||||
const loopIterationVars: Suggestion[] = [];
|
|
||||||
|
|
||||||
return [...getFilteredVariableList(selectedNode?.data?.type, key), ...loopIterationVars];
|
|
||||||
}
|
|
||||||
return getFilteredVariableList(selectedNode?.data?.type, key);
|
|
||||||
})()
|
|
||||||
}
|
}
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'memoryConfig') {
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
key={key}
|
|
||||||
name={key}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
<MemoryConfig
|
|
||||||
parentName={key}
|
|
||||||
options={getFilteredVariableList('llm')}
|
|
||||||
/>
|
|
||||||
</Form.Item>
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if (config.type === 'conditionList') {
|
|
||||||
return (
|
|
||||||
<Form.Item
|
|
||||||
key={key}
|
|
||||||
name={key}
|
|
||||||
noStyle
|
|
||||||
>
|
|
||||||
<ConditionList
|
|
||||||
parentName={key}
|
|
||||||
options={(() => {
|
|
||||||
const cycleVars = values?.cycle_vars || [];
|
|
||||||
const cycleVarSuggestions: Suggestion[] = cycleVars.filter(vo => vo.name && vo.name.trim() !== '').map((cycleVar: any) => ({
|
|
||||||
key: `${selectedNode.id}_cycle_${cycleVar.name}`,
|
|
||||||
label: cycleVar.name,
|
|
||||||
type: 'variable',
|
|
||||||
dataType: cycleVar.type || 'String',
|
|
||||||
value: `${selectedNode.getData().id}.${cycleVar.name}`,
|
|
||||||
nodeData: selectedNode.getData(),
|
|
||||||
}));
|
|
||||||
|
|
||||||
return [...getFilteredVariableList(selectedNode?.data?.type, key), ...cycleVarSuggestions];
|
if (selectedNode?.data?.type === 'start' && key === 'variables' && config.type === 'define') {
|
||||||
})()}
|
return (
|
||||||
selectedNode={selectedNode}
|
<Form.Item key={key} name={key} className="rb:mb-0!">
|
||||||
graphRef={graphRef}
|
<VariableList
|
||||||
addBtnText={t('workflow.config.addCase')}
|
parentName={key}
|
||||||
/>
|
selectedNode={selectedNode}
|
||||||
</Form.Item>
|
config={config}
|
||||||
)
|
/>
|
||||||
}
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
if (key === 'vision_input' && !values?.vision) {
|
if (key === 'model_id' && selectedNode?.data?.type === 'llm') {
|
||||||
return null
|
return <ModelConfig />
|
||||||
}
|
}
|
||||||
|
if (selectedNode?.data?.type === 'llm' && key === 'messages' && config.type === 'define') {
|
||||||
|
// 为llm节点且isArray=true时添加context变量支持
|
||||||
|
let contextVariableList = [...getFilteredVariableList('llm')];
|
||||||
|
const isArrayMode = config.isArray !== false; // 默认为true
|
||||||
|
|
||||||
return (
|
if (isArrayMode) {
|
||||||
<Form.Item
|
const contextKey = `${selectedNode.id}_context`;
|
||||||
key={key}
|
const hasContextVariable = contextVariableList.some(v => v.key === contextKey);
|
||||||
name={key}
|
|
||||||
label={key === 'vision_input'
|
if (!hasContextVariable) {
|
||||||
? undefined : key === 'parallel_count'
|
contextVariableList.unshift({
|
||||||
? <span className="rb:text-[10px] rb:text-[#5B6167] rb:leading-3.5 rb:-mb-1!">{t(`workflow.config.${selectedNode?.data?.type}.${key}`)}</span>
|
key: contextKey,
|
||||||
: t(`workflow.config.${selectedNode?.data?.type}.${key}`)
|
label: 'context',
|
||||||
}
|
type: 'variable',
|
||||||
layout={config.type === 'switch' ? 'horizontal' : 'vertical'}
|
dataType: 'String',
|
||||||
className={
|
value: `context`,
|
||||||
key === 'parallel' && values?.parallel
|
nodeData: selectedNode.getData(),
|
||||||
? 'rb:mb-1!'
|
isContext: true,
|
||||||
: key === 'vision' && values?.vision
|
});
|
||||||
? 'rb:mb-2!'
|
}
|
||||||
: key === 'group' && values?.group
|
}
|
||||||
? 'rb:mb-3!'
|
return (
|
||||||
: ''
|
<Form.Item key={key} name={key}>
|
||||||
}
|
<MessageEditor
|
||||||
hidden={Boolean(config.hidden)}
|
key={key}
|
||||||
>
|
options={contextVariableList.filter(variable => variable.nodeData?.type !== 'knowledge-retrieval')}
|
||||||
{config.type === 'input'
|
parentName={key}
|
||||||
? <Input placeholder={t('common.pleaseEnter')} />
|
placeholder={t(config.placeholder || 'common.pleaseSelect')}
|
||||||
: config.type === 'textarea'
|
|
||||||
? <Input.TextArea placeholder={t('common.pleaseEnter')} />
|
|
||||||
: config.type === 'select'
|
|
||||||
? <Select
|
|
||||||
options={config.needTranslation ? (config.options || []).map(vo => ({ ...vo, label: t(vo.label) })) : config.options}
|
|
||||||
placeholder={t('common.pleaseSelect')}
|
|
||||||
/>
|
|
||||||
: config.type === 'inputNumber'
|
|
||||||
? <InputNumber
|
|
||||||
placeholder={t('common.pleaseEnter')}
|
|
||||||
className="rb:w-full!"
|
|
||||||
onChange={(value) => form.setFieldValue(key, value)}
|
|
||||||
/>
|
|
||||||
: config.type === 'slider'
|
|
||||||
? <RbSlider
|
|
||||||
min={config.min}
|
|
||||||
max={config.max}
|
|
||||||
step={config.step || 0.01}
|
|
||||||
isInput={true}
|
|
||||||
size="small"
|
size="small"
|
||||||
/>
|
/>
|
||||||
: config.type === 'customSelect'
|
</Form.Item>
|
||||||
? <CustomSelect
|
)
|
||||||
placeholder={t('common.pleaseSelect')}
|
}
|
||||||
url={config.url as string}
|
if (selectedNode?.data?.type === 'iteration' && key === 'output_type') {
|
||||||
params={config.params}
|
return (<Form.Item key={key} name={key} hidden />)
|
||||||
hasAll={false}
|
}
|
||||||
valueKey={config.valueKey}
|
if (config.type === 'define') {
|
||||||
labelKey={config.labelKey}
|
return null
|
||||||
size="small"
|
}
|
||||||
/>
|
|
||||||
: config.type === 'variableList'
|
|
||||||
? <VariableSelect
|
|
||||||
placeholder={t(config.placeholder || 'common.pleaseSelect')}
|
|
||||||
options={(() => {
|
|
||||||
const baseVariableList = getFilteredVariableList(selectedNode?.data?.type, key);
|
|
||||||
// Apply filtering if specified in config
|
|
||||||
if (config.filterNodeTypes || config.filterVariableNames) {
|
|
||||||
return baseVariableList.filter(variable => {
|
|
||||||
const nodeTypeMatch = !config.filterNodeTypes ||
|
|
||||||
(Array.isArray(config.filterNodeTypes) && config.filterNodeTypes.includes(variable.nodeData?.type));
|
|
||||||
const variableNameMatch = !config.filterVariableNames ||
|
|
||||||
(Array.isArray(config.filterVariableNames) && config.filterVariableNames.includes(variable.label));
|
|
||||||
return nodeTypeMatch || variableNameMatch;
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if (config.onFilterVariableNames) {
|
|
||||||
return baseVariableList.filter(variable => Array.isArray(config.onFilterVariableNames) && config.onFilterVariableNames.includes(variable.label));
|
|
||||||
}
|
|
||||||
// Filter child nodes for iteration output
|
|
||||||
if (config.filterChildNodes && selectedNode) {
|
|
||||||
const graph = graphRef.current;
|
|
||||||
if (!graph) return [];
|
|
||||||
|
|
||||||
const nodes = graph.getNodes();
|
if (config.type === 'knowledge') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
key={key}
|
||||||
|
name={key}
|
||||||
|
>
|
||||||
|
<Knowledge />
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
// Find child nodes whose cycle field equals parent node's ID
|
if (config.type === 'messageEditor') {
|
||||||
const childNodes = nodes.filter(node => {
|
return (
|
||||||
const nodeData = node.getData();
|
<Form.Item key={key} name={key} label={selectedNode?.data?.type === 'memory-write' ? t(`workflow.config.${selectedNode?.data?.type}.${key}`) : undefined}>
|
||||||
return nodeData?.cycle === selectedNode.id;
|
<MessageEditor
|
||||||
});
|
title={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||||
|
placeholder={t(config.placeholder || 'common.pleaseEnter')}
|
||||||
|
isArray={!!config.isArray}
|
||||||
|
parentName={key}
|
||||||
|
language={config.language as LexicalEditorProps['language']}
|
||||||
|
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
||||||
|
titleVariant={config.titleVariant}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
return baseVariableList.filter(variable =>
|
if (config.type === 'paramList') {
|
||||||
childNodes.some(node => node.id === variable.nodeData?.id) || selectedNode?.data?.type === 'iteration' && key === 'output' && variable.value.includes('sys.')
|
return (
|
||||||
);
|
<Form.Item key={key} name={key}>
|
||||||
}
|
<ParamsList
|
||||||
return baseVariableList;
|
label={t(`workflow.config.${selectedNode?.data?.type}.${key}`)}
|
||||||
})()}
|
/>
|
||||||
onChange={(value, option) => handleChangeVariableList(value, option, key)}
|
</Form.Item>
|
||||||
size="small"
|
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'groupVariableList') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}>
|
||||||
|
<GroupVariableList
|
||||||
|
name={key}
|
||||||
|
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
||||||
|
isCanAdd={!!(values as any)?.group}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'caseList') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key} noStyle>
|
||||||
|
<CaseList
|
||||||
|
name={key}
|
||||||
|
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
||||||
|
selectedNode={selectedNode}
|
||||||
|
graphRef={graphRef}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'cycleVarsList') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}>
|
||||||
|
<CycleVarsList
|
||||||
|
size="small"
|
||||||
|
parentName={key}
|
||||||
|
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'assignmentList') {
|
||||||
|
return (
|
||||||
|
<Form.Item key={key} name={key}>
|
||||||
|
<AssignmentList
|
||||||
|
parentName={key}
|
||||||
|
options={(() => {
|
||||||
|
if (config.filterLoopIterationVars) {
|
||||||
|
const loopIterationVars: Suggestion[] = [];
|
||||||
|
|
||||||
|
return [...getFilteredVariableList(selectedNode?.data?.type, key), ...loopIterationVars];
|
||||||
|
}
|
||||||
|
return getFilteredVariableList(selectedNode?.data?.type, key);
|
||||||
|
})()
|
||||||
|
}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'memoryConfig') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
key={key}
|
||||||
|
name={key}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<MemoryConfig
|
||||||
|
parentName={key}
|
||||||
|
options={getFilteredVariableList('llm')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
if (config.type === 'conditionList') {
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
key={key}
|
||||||
|
name={key}
|
||||||
|
noStyle
|
||||||
|
>
|
||||||
|
<ConditionList
|
||||||
|
parentName={key}
|
||||||
|
options={(() => {
|
||||||
|
const cycleVars = values?.cycle_vars || [];
|
||||||
|
const cycleVarSuggestions: Suggestion[] = cycleVars.filter(vo => vo.name && vo.name.trim() !== '').map((cycleVar: any) => ({
|
||||||
|
key: `${selectedNode.id}_cycle_${cycleVar.name}`,
|
||||||
|
label: cycleVar.name,
|
||||||
|
type: 'variable',
|
||||||
|
dataType: cycleVar.type || 'String',
|
||||||
|
value: `${selectedNode.getData().id}.${cycleVar.name}`,
|
||||||
|
nodeData: selectedNode.getData(),
|
||||||
|
}));
|
||||||
|
|
||||||
|
return [...getFilteredVariableList(selectedNode?.data?.type, key), ...cycleVarSuggestions];
|
||||||
|
})()}
|
||||||
|
selectedNode={selectedNode}
|
||||||
|
graphRef={graphRef}
|
||||||
|
addBtnText={t('workflow.config.addCase')}
|
||||||
|
/>
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
if (key === 'vision_input' && !values?.vision) {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
|
||||||
|
return (
|
||||||
|
<Form.Item
|
||||||
|
key={key}
|
||||||
|
name={key}
|
||||||
|
label={key === 'vision_input'
|
||||||
|
? undefined : key === 'parallel_count'
|
||||||
|
? <span className="rb:text-[10px] rb:text-[#5B6167] rb:leading-3.5 rb:-mb-1!">{t(`workflow.config.${selectedNode?.data?.type}.${key}`)}</span>
|
||||||
|
: t(`workflow.config.${selectedNode?.data?.type}.${key}`)
|
||||||
|
}
|
||||||
|
layout={config.type === 'switch' ? 'horizontal' : 'vertical'}
|
||||||
|
className={
|
||||||
|
key === 'parallel' && values?.parallel
|
||||||
|
? 'rb:mb-1!'
|
||||||
|
: key === 'vision' && values?.vision
|
||||||
|
? 'rb:mb-2!'
|
||||||
|
: key === 'group' && values?.group
|
||||||
|
? 'rb:mb-3!'
|
||||||
|
: ''
|
||||||
|
}
|
||||||
|
hidden={Boolean(config.hidden)}
|
||||||
|
>
|
||||||
|
{config.type === 'input'
|
||||||
|
? <Input placeholder={t('common.pleaseEnter')} />
|
||||||
|
: config.type === 'textarea'
|
||||||
|
? <Input.TextArea placeholder={t('common.pleaseEnter')} />
|
||||||
|
: config.type === 'select'
|
||||||
|
? <Select
|
||||||
|
options={config.needTranslation ? (config.options || []).map(vo => ({ ...vo, label: t(vo.label) })) : config.options}
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
/>
|
/>
|
||||||
: config.type === 'switch'
|
: config.type === 'inputNumber'
|
||||||
? <Switch onChange={
|
? <InputNumber
|
||||||
key === 'group'
|
placeholder={t('common.pleaseEnter')}
|
||||||
? () => { form.setFieldValue('group_variables', []) }
|
className="rb:w-full!"
|
||||||
: key === 'vision'
|
onChange={(value) => form.setFieldValue(key, value)}
|
||||||
? () => { form.setFieldValue('vision_input', undefined) }
|
/>
|
||||||
: undefined
|
: config.type === 'slider'
|
||||||
} />
|
? <RbSlider
|
||||||
: config.type === 'categoryList'
|
min={config.min}
|
||||||
? <CategoryList
|
max={config.max}
|
||||||
parentName={key}
|
step={config.step || 0.01}
|
||||||
selectedNode={selectedNode}
|
isInput={true}
|
||||||
graphRef={graphRef}
|
size="small"
|
||||||
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
|
||||||
/>
|
/>
|
||||||
: config.type === 'editor'
|
: config.type === 'modelSelect'
|
||||||
? <Editor options={variableList} variant="outlined" size="small" placeholder={config.placeholder || t('common.pleaseEnter')} />
|
? <ModelSelect
|
||||||
: null
|
placeholder={t('common.pleaseSelect')}
|
||||||
}
|
params={config.params}
|
||||||
</Form.Item>
|
size="small"
|
||||||
)
|
className="rb:w-full!"
|
||||||
})
|
/>
|
||||||
|
: config.type === 'customSelect'
|
||||||
|
? <CustomSelect
|
||||||
|
placeholder={t('common.pleaseSelect')}
|
||||||
|
url={config.url as string}
|
||||||
|
params={config.params}
|
||||||
|
hasAll={false}
|
||||||
|
valueKey={config.valueKey}
|
||||||
|
labelKey={config.labelKey}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
: config.type === 'variableList'
|
||||||
|
? <VariableSelect
|
||||||
|
placeholder={t(config.placeholder || 'common.pleaseSelect')}
|
||||||
|
options={(() => {
|
||||||
|
const baseVariableList = getFilteredVariableList(selectedNode?.data?.type, key);
|
||||||
|
// Apply filtering if specified in config
|
||||||
|
if (config.filterNodeTypes || config.filterVariableNames) {
|
||||||
|
return baseVariableList.filter(variable => {
|
||||||
|
const nodeTypeMatch = !config.filterNodeTypes ||
|
||||||
|
(Array.isArray(config.filterNodeTypes) && config.filterNodeTypes.includes(variable.nodeData?.type));
|
||||||
|
const variableNameMatch = !config.filterVariableNames ||
|
||||||
|
(Array.isArray(config.filterVariableNames) && config.filterVariableNames.includes(variable.label));
|
||||||
|
return nodeTypeMatch || variableNameMatch;
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if (config.onFilterVariableNames) {
|
||||||
|
return baseVariableList.filter(variable => Array.isArray(config.onFilterVariableNames) && config.onFilterVariableNames.includes(variable.label));
|
||||||
|
}
|
||||||
|
// Filter child nodes for iteration output
|
||||||
|
if (config.filterChildNodes && selectedNode) {
|
||||||
|
const graph = graphRef.current;
|
||||||
|
if (!graph) return [];
|
||||||
|
|
||||||
|
const nodes = graph.getNodes();
|
||||||
|
|
||||||
|
// Find child nodes whose cycle field equals parent node's ID
|
||||||
|
const childNodes = nodes.filter(node => {
|
||||||
|
const nodeData = node.getData();
|
||||||
|
return nodeData?.cycle === selectedNode.id;
|
||||||
|
});
|
||||||
|
|
||||||
|
return baseVariableList.filter(variable =>
|
||||||
|
childNodes.some(node => node.id === variable.nodeData?.id) || selectedNode?.data?.type === 'iteration' && key === 'output' && variable.value.includes('sys.')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
return baseVariableList;
|
||||||
|
})()}
|
||||||
|
onChange={(value, option) => handleChangeVariableList(value, option, key)}
|
||||||
|
size="small"
|
||||||
|
/>
|
||||||
|
: config.type === 'switch'
|
||||||
|
? <Switch onChange={
|
||||||
|
key === 'group'
|
||||||
|
? () => { form.setFieldValue('group_variables', []) }
|
||||||
|
: key === 'vision'
|
||||||
|
? () => { form.setFieldValue('vision_input', undefined) }
|
||||||
|
: undefined
|
||||||
|
} />
|
||||||
|
: config.type === 'categoryList'
|
||||||
|
? <CategoryList
|
||||||
|
parentName={key}
|
||||||
|
selectedNode={selectedNode}
|
||||||
|
graphRef={graphRef}
|
||||||
|
options={getFilteredVariableList(selectedNode?.data?.type, key)}
|
||||||
|
/>
|
||||||
|
: config.type === 'editor'
|
||||||
|
? <Editor options={variableList} variant="outlined" size="small" placeholder={config.placeholder || t('common.pleaseEnter')} />
|
||||||
|
: null
|
||||||
|
}
|
||||||
|
</Form.Item>
|
||||||
|
)
|
||||||
|
})
|
||||||
}
|
}
|
||||||
</Form>
|
</Form>
|
||||||
|
|
||||||
@@ -829,7 +837,7 @@ const Properties: FC<PropertiesProps> = ({
|
|||||||
className={clsx("rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/common/caret_right_outlined.svg')]", {
|
className={clsx("rb:size-3 rb:bg-cover rb:bg-[url('src/assets/images/common/caret_right_outlined.svg')]", {
|
||||||
'rb:rotate-90': !outputCollapsed
|
'rb:rotate-90': !outputCollapsed
|
||||||
})}
|
})}
|
||||||
></div>
|
></div>
|
||||||
</Flex>
|
</Flex>
|
||||||
{!outputCollapsed && currentNodeVariables.map(vo => (
|
{!outputCollapsed && currentNodeVariables.map(vo => (
|
||||||
<Flex key={vo.value} gap={4}>
|
<Flex key={vo.value} gap={4}>
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
* @Author: ZhaoYing
|
* @Author: ZhaoYing
|
||||||
* @Date: 2026-02-03 15:06:18
|
* @Date: 2026-02-03 15:06:18
|
||||||
* @Last Modified by: ZhaoYing
|
* @Last Modified by: ZhaoYing
|
||||||
* @Last Modified time: 2026-03-06 14:52:02
|
* @Last Modified time: 2026-03-07 17:10:59
|
||||||
*/
|
*/
|
||||||
import LoopNode from './components/Nodes/LoopNode';
|
import LoopNode from './components/Nodes/LoopNode';
|
||||||
import NormalNode from './components/Nodes/NormalNode';
|
import NormalNode from './components/Nodes/NormalNode';
|
||||||
@@ -34,8 +34,6 @@ import memoryWriteIcon from '@/assets/images/workflow/memory-write.svg'
|
|||||||
import unknownIcon from '@/assets/images/workflow/unknown.svg'
|
import unknownIcon from '@/assets/images/workflow/unknown.svg'
|
||||||
|
|
||||||
import { memoryConfigListUrl } from '@/api/memory'
|
import { memoryConfigListUrl } from '@/api/memory'
|
||||||
|
|
||||||
import { getModelListUrl } from '@/api/models'
|
|
||||||
import type { NodeLibrary } from './types'
|
import type { NodeLibrary } from './types'
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@@ -104,8 +102,7 @@ export const nodeLibrary: NodeLibrary[] = [
|
|||||||
config: {
|
config: {
|
||||||
model_id: {
|
model_id: {
|
||||||
type: 'define',
|
type: 'define',
|
||||||
url: getModelListUrl,
|
params: { type: 'llm,chat' }, // llm/chat
|
||||||
params: { type: 'llm,chat', pagesize: 100, is_active: true }, // llm/chat
|
|
||||||
valueKey: 'id',
|
valueKey: 'id',
|
||||||
labelKey: 'name',
|
labelKey: 'name',
|
||||||
},
|
},
|
||||||
@@ -168,11 +165,8 @@ export const nodeLibrary: NodeLibrary[] = [
|
|||||||
{ type: "parameter-extractor", icon: parameterExtractionIcon,
|
{ type: "parameter-extractor", icon: parameterExtractionIcon,
|
||||||
config: {
|
config: {
|
||||||
model_id: {
|
model_id: {
|
||||||
type: 'customSelect',
|
type: 'modelSelect',
|
||||||
url: getModelListUrl,
|
params: { type: 'llm,chat' }, // llm/chat
|
||||||
params: { type: 'llm,chat', pagesize: 100, is_active: true }, // llm/chat
|
|
||||||
valueKey: 'id',
|
|
||||||
labelKey: 'name',
|
|
||||||
},
|
},
|
||||||
text: {
|
text: {
|
||||||
type: 'variableList',
|
type: 'variableList',
|
||||||
@@ -260,11 +254,8 @@ export const nodeLibrary: NodeLibrary[] = [
|
|||||||
{ type: "question-classifier", icon: questionClassifierIcon,
|
{ type: "question-classifier", icon: questionClassifierIcon,
|
||||||
config: {
|
config: {
|
||||||
model_id: {
|
model_id: {
|
||||||
type: 'customSelect',
|
type: 'modelSelect',
|
||||||
url: getModelListUrl,
|
params: { type: 'llm,chat' }, // llm/chat
|
||||||
params: { type: 'llm,chat', pagesize: 100, is_active: true }, // llm/chat
|
|
||||||
valueKey: 'id',
|
|
||||||
labelKey: 'name',
|
|
||||||
},
|
},
|
||||||
input_variable: {
|
input_variable: {
|
||||||
type: 'variableList',
|
type: 'variableList',
|
||||||
|
|||||||
Reference in New Issue
Block a user