feat(web): add association between models and conversation features

This commit is contained in:
zhaoying
2026-03-24 11:06:27 +08:00
parent cbec2c1356
commit 3369b702e4
8 changed files with 125 additions and 75 deletions

View File

@@ -1440,6 +1440,7 @@ export const en = {
confirmCopyDesc: 'Are you sure to copy 【{{app}}】 app?', confirmCopyDesc: 'Are you sure to copy 【{{app}}】 app?',
noShareAuth: 'No permission to share apps', noShareAuth: 'No permission to share apps',
appCount: '{{count}} apps shared to this space', appCount: '{{count}} apps shared to this space',
resetFeaturesTip: 'Please reconfigure the [Conversation Features - File Upload] settings',
}, },
userMemory: { userMemory: {
userMemory: 'User Memory', userMemory: 'User Memory',

View File

@@ -818,6 +818,7 @@ export const zh = {
confirmCopyDesc: '确定复制【{{app}}】应用?', confirmCopyDesc: '确定复制【{{app}}】应用?',
noShareAuth: '无共享应用的权限', noShareAuth: '无共享应用的权限',
appCount: '{{count}}个应用共享到此空间', appCount: '{{count}}个应用共享到此空间',
resetFeaturesTip: '请重新配置【对话功能-文件上传】功能',
}, },
table: { table: {
totalRecords: '共 {{total}} 条记录' totalRecords: '共 {{total}} 条记录'

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:29:21 * @Date: 2026-02-03 16:29:21
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-20 11:36:49 * @Last Modified time: 2026-03-24 11:02:34
*/ */
import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react'; import { useEffect, useRef, useState, forwardRef, useImperativeHandle } from 'react';
import { useTranslation } from 'react-i18next' import { useTranslation } from 'react-i18next'
@@ -132,9 +132,20 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
*/ */
const refresh = (vo: ModelConfig, type: Source) => { const refresh = (vo: ModelConfig, type: Source) => {
if (type === 'model') { if (type === 'model') {
const { default_model_config_id, ...rest } = vo const { default_model_config_id, capability, ...rest } = vo
if (default_model_config_id !== values.default_model_config_id) {
const fileUpload = { ...values.features?.file_upload }
Object.keys(fileUpload).forEach(key => {
if (key.includes('enabled')) {
(fileUpload as Record<string, any>)[key] = false
}
})
form.setFieldValue(['features', 'file_upload'], fileUpload)
message.warning(t('application.resetFeaturesTip'))
}
form.setFieldsValue({ form.setFieldsValue({
default_model_config_id, default_model_config_id,
capability,
model_parameters: {...rest} model_parameters: {...rest}
}) })
if (default_model_config_id === values?.default_model_config_id) { if (default_model_config_id === values?.default_model_config_id) {
@@ -348,13 +359,14 @@ const Agent = forwardRef<AgentRef, { onFeaturesLoad?: (features: FeaturesConfigF
{defaultModel?.name || t('application.chooseModel')} {defaultModel?.name || t('application.chooseModel')}
</Button> </Button>
<Space size={12}> <Space size={12}>
<FeaturesConfig value={values?.features as FeaturesConfigForm} refresh={handleSaveFeaturesConfig} /> <FeaturesConfig value={values?.features as FeaturesConfigForm} capability={values?.capability || []} refresh={handleSaveFeaturesConfig} />
<Button type="primary" onClick={() => handleSave()}> <Button type="primary" onClick={() => handleSave()}>
{t('common.save')} {t('common.save')}
</Button> </Button>
</Space> </Space>
</Flex> </Flex>
<Form.Item name="default_model_config_id" hidden noStyle></Form.Item> <Form.Item name="default_model_config_id" hidden noStyle></Form.Item>
<Form.Item name="capability" hidden noStyle></Form.Item>
<Form.Item name="model_parameters" hidden noStyle></Form.Item> <Form.Item name="model_parameters" hidden noStyle></Form.Item>
<Form.Item name="features" hidden noStyle></Form.Item> <Form.Item name="features" hidden noStyle></Form.Item>
<Card <Card

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:27:56 * @Date: 2026-02-03 16:27:56
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-18 15:38:14 * @Last Modified time: 2026-03-24 10:59:37
*/ */
/** /**
* Copy Application Modal * Copy Application Modal
@@ -19,10 +19,12 @@ import RbModal from '@/components/RbModal'
import SwitchFormItem from '@/components/FormItem/SwitchFormItem' import SwitchFormItem from '@/components/FormItem/SwitchFormItem'
import FileUploadSettingModal from './FileUploadSettingModal' import FileUploadSettingModal from './FileUploadSettingModal'
import type { Application } from '@/views/ApplicationManagement/types'; import type { Application } from '@/views/ApplicationManagement/types';
import type { Capability } from '@/views/ModelManagement/types'
interface FeaturesConfigModalProps { interface FeaturesConfigModalProps {
refresh: (value: FeaturesConfigForm) => void; refresh: (value: FeaturesConfigForm) => void;
source?: Application['type']; source?: Application['type'];
capability?: Capability[];
} }
const max_file_count = 1; const max_file_count = 1;
/** /**
@@ -31,6 +33,7 @@ const max_file_count = 1;
const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigModalProps>(({ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigModalProps>(({
refresh, refresh,
source, source,
capability,
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@@ -47,7 +50,6 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
/** Open modal */ /** Open modal */
const handleOpen = (initValue: FeaturesConfigForm) => { const handleOpen = (initValue: FeaturesConfigForm) => {
setVisible(true); setVisible(true);
console.log('initValue', initValue)
form.setFieldsValue(initValue) form.setFieldsValue(initValue)
}; };
/** Copy application with new name */ /** Copy application with new name */
@@ -64,6 +66,22 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
form.setFieldValue('file_upload', { ...settings, enabled: values?.file_upload?.enabled ?? false }) form.setFieldValue('file_upload', { ...settings, enabled: values?.file_upload?.enabled ?? false })
} }
const formatFileTypeOptions = (fu: FeaturesConfigForm['file_upload']) => {
let options = [{ type: 'document', enabled: fu.document_enabled, maxSize: fu.document_max_size_mb }]
if (!capability) return options
if (capability.includes('vision')) {
options.push({ type: 'image', enabled: fu.image_enabled, maxSize: fu.image_max_size_mb })
}
if (capability.includes('audio')) {
options.push({ type: 'audio', enabled: fu.audio_enabled, maxSize: fu.audio_max_size_mb })
}
if (capability.includes('video')) {
options.push({ type: 'video', enabled: fu.video_enabled, maxSize: fu.video_max_size_mb })
}
return options.filter(item => item.enabled)
}
/** Expose methods to parent component */ /** Expose methods to parent component */
useImperativeHandle(ref, () => ({ useImperativeHandle(ref, () => ({
handleOpen, handleOpen,
@@ -109,22 +127,19 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
/> />
{values?.file_upload?.enabled && (() => { {values?.file_upload?.enabled && (() => {
const fu = values.file_upload const fu = values.file_upload
const types = [ // 'vision' | 'audio' | 'video'
{ type: 'image', enabled: fu.image_enabled, maxSize: fu.image_max_size_mb }, const filterTypes = formatFileTypeOptions(fu)
{ type: 'audio', enabled: fu.audio_enabled, maxSize: fu.audio_max_size_mb }, console.log('filterTypes', filterTypes)
{ type: 'document', enabled: fu.document_enabled, maxSize: fu.document_max_size_mb }, return filterTypes.length > 0 ? <>
{ type: 'video', enabled: fu.video_enabled, maxSize: fu.video_max_size_mb },
].filter(item => item.enabled)
return types.length > 0 ? <>
<Flex gap={12} className="rb:py-2!"> <Flex gap={12} className="rb:py-2!">
<div className="rb:flex-1 rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:bg-white rb:text-[12px]"> <div className="rb:flex-1 rb:border rb:border-[#DFE4ED] rb:rounded-lg rb:bg-white rb:text-[12px]">
<div className="rb:grid rb:grid-cols-2 rb:gap-2 rb:text-[12px] rb:text-[#5B6167] rb:border-b rb:border-b-[#DFE4ED]"> <div className="rb:grid rb:grid-cols-2 rb:gap-2 rb:text-[12px] rb:text-[#5B6167] rb:border-b rb:border-b-[#DFE4ED]">
<div className="rb:px-3 rb:py-1">{t(`application.supportedTypes`)}</div> <div className="rb:px-3 rb:py-1">{t(`application.supportedTypes`)}</div>
<div className="rb:px-3 rb:py-1">{t('application.singleMaxSize')}</div> <div className="rb:px-3 rb:py-1">{t('application.singleMaxSize')}</div>
</div> </div>
{types.map((item, index) => ( {filterTypes.map((item, index) => (
<div key={item.type} className={clsx('rb:grid rb:grid-cols-2 rb:gap-2', { <div key={item.type} className={clsx('rb:grid rb:grid-cols-2 rb:gap-2', {
'rb:border-b rb:border-b-[#DFE4ED]': index !== types.length - 1 'rb:border-b rb:border-b-[#DFE4ED]': index !== filterTypes.length - 1
})}> })}>
<div className="rb:px-3 rb:py-1">{t(`application.${item.type}`)}</div> <div className="rb:px-3 rb:py-1">{t(`application.${item.type}`)}</div>
<div className="rb:px-3 rb:py-1">{item.maxSize} MB</div> <div className="rb:px-3 rb:py-1">{item.maxSize} MB</div>
@@ -148,6 +163,7 @@ const FeaturesConfigModal = forwardRef<FeaturesConfigModalRef, FeaturesConfigMod
<FileUploadSettingModal <FileUploadSettingModal
ref={fileUploadSettingModalRef} ref={fileUploadSettingModalRef}
onSave={handleSaveSettings} onSave={handleSaveSettings}
capability={capability}
/> />
</> </>
); );

View File

@@ -2,15 +2,16 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-05 * @Date: 2026-03-05
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 20:19:14 * @Last Modified time: 2026-03-24 11:00:14
*/ */
import { forwardRef, useImperativeHandle, useState } from 'react'; import { forwardRef, useImperativeHandle, useState, useMemo } from 'react';
import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd'; import { Form, InputNumber, Flex, Switch, Row, Col, Radio } from 'antd';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
import clsx from 'clsx'; import clsx from 'clsx';
import RbModal from '@/components/RbModal'; import RbModal from '@/components/RbModal';
import type { FeaturesConfigForm } from '../../types' import type { FeaturesConfigForm } from '../../types'
import type { Capability } from '@/views/ModelManagement/types'
type FileUpload = Omit<FeaturesConfigForm['file_upload'], 'settings'> type FileUpload = Omit<FeaturesConfigForm['file_upload'], 'settings'>
@@ -21,51 +22,49 @@ interface FileUploadSettingModalRef {
interface FileUploadSettingModalProps { interface FileUploadSettingModalProps {
onSave: (values: FileUpload) => void; onSave: (values: FileUpload) => void;
capability?: Capability[];
}
const documentType = {
type: 'document',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/txt.svg')]"></div>,
formats: [
"pdf",
"docx",
"doc",
"xlsx",
"xls",
"txt",
"csv",
"json",
"md",
],
}
const imageType = {
type: 'image',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/image.svg')]"></div>,
formats: [
"png",
"jpg",
"jpeg"
],
}
const audioType = {
type: 'audio',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/audio.svg')]"></div>,
formats: [
"mp3",
"wav",
"m4a",
],
}
const videoType = {
type: 'video',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/video.svg')]"></div>,
formats: [
"mp4",
"mov",
],
} }
const fileTypeOptions = [
{
type: 'document',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/txt.svg')]"></div>,
formats: [
"pdf",
"docx",
"doc",
"xlsx",
"xls",
"txt",
"csv",
"json",
"md",
],
},
{
type: 'image',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/image.svg')]"></div>,
formats: [
"png",
"jpg",
"jpeg"
],
},
{
type: 'audio',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/audio.svg')]"></div>,
formats: [
"mp3",
"wav",
"m4a",
],
},
{
type: 'video',
icon: <div className="rb:size-9 rb:bg-cover rb:bg-[url('@/assets/images/file/video.svg')]"></div>,
formats: [
"mp4",
"mov",
],
},
];
const defaultValues: FileUpload = { const defaultValues: FileUpload = {
enabled: false, enabled: false,
@@ -108,6 +107,7 @@ const defaultValues: FileUpload = {
const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadSettingModalProps>(({ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadSettingModalProps>(({
onSave, onSave,
capability,
}, ref) => { }, ref) => {
const { t } = useTranslation(); const { t } = useTranslation();
const [visible, setVisible] = useState(false); const [visible, setVisible] = useState(false);
@@ -148,6 +148,15 @@ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadS
handleClose handleClose
})); }));
const fileTypeOptions = useMemo(() => {
let options = [documentType]
if (!capability) return options
if (capability.includes('vision')) options = [...options, imageType]
if (capability.includes('audio')) options = [...options, audioType]
if (capability.includes('video')) options = [...options, videoType]
return options
}, [capability])
return ( return (
<RbModal <RbModal
title={t('application.settings')} title={t('application.settings')}
@@ -167,7 +176,6 @@ const FileUploadSettingModal = forwardRef<FileUploadSettingModalRef, FileUploadS
</Radio.Group> </Radio.Group>
</Form.Item> </Form.Item>
{/* <div className="rb:text-[12px] rb:text-[#5B6167] rb:mb-1">{t('application.maxCount')}</div> */}
<Form.Item label={t('application.maxCount')} name="max_file_count" hidden> <Form.Item label={t('application.maxCount')} name="max_file_count" hidden>
<InputNumber min={1} max={20} precision={0} className="rb:w-full!" placeholder={t('common.pleaseEnter')} /> <InputNumber min={1} max={20} precision={0} className="rb:w-full!" placeholder={t('common.pleaseEnter')} />
</Form.Item> </Form.Item>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-03-13 17:20:21 * @Date: 2026-03-13 17:20:21
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-18 15:38:59 * @Last Modified time: 2026-03-24 11:00:25
*/ */
import { type FC, useRef } from 'react'; import { type FC, useRef } from 'react';
import { useTranslation } from 'react-i18next'; import { useTranslation } from 'react-i18next';
@@ -11,6 +11,7 @@ import { Button } from 'antd';
import FeaturesConfigModal from './FeaturesConfigModal' import FeaturesConfigModal from './FeaturesConfigModal'
import type { FeaturesConfigModalRef, FeaturesConfigForm } from '../../types' import type { FeaturesConfigModalRef, FeaturesConfigForm } from '../../types'
import type { Application } from '@/views/ApplicationManagement/types'; import type { Application } from '@/views/ApplicationManagement/types';
import type { Capability } from '@/views/ModelManagement/types'
/** Props for the FeaturesConfig component */ /** Props for the FeaturesConfig component */
interface FeaturesConfigProps { interface FeaturesConfigProps {
@@ -19,12 +20,14 @@ interface FeaturesConfigProps {
/** Callback to propagate updated config back to the parent */ /** Callback to propagate updated config back to the parent */
refresh: (value: FeaturesConfigForm) => void; refresh: (value: FeaturesConfigForm) => void;
source?: Application['type']; source?: Application['type'];
capability?: Capability[];
} }
const FeaturesConfig: FC<FeaturesConfigProps> = ({ const FeaturesConfig: FC<FeaturesConfigProps> = ({
value, value,
refresh, refresh,
source source,
capability
}) => { }) => {
const { t } = useTranslation(); const { t } = useTranslation();
// Ref used to imperatively open the config modal // Ref used to imperatively open the config modal
@@ -46,6 +49,7 @@ const FeaturesConfig: FC<FeaturesConfigProps> = ({
ref={funConfigModalRef} ref={funConfigModalRef}
refresh={refresh} refresh={refresh}
source={source} source={source}
capability={capability}
/> />
</> </>
) )

View File

@@ -1,8 +1,8 @@
/* /*
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:28:07 * @Date: 2026-02-03 16:28:07
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-02-03 16:28:07 * @Last Modified time: 2026-03-24 10:59:20
*/ */
/** /**
* Model Configuration Modal * Model Configuration Modal
@@ -105,6 +105,8 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
const handleChange = (_value: string, option: ModelListItem | ModelListItem[] | undefined) => { const handleChange = (_value: string, option: ModelListItem | ModelListItem[] | undefined) => {
if (source === 'chat') { if (source === 'chat') {
form.setFieldValue('label', (option as ModelListItem).name) form.setFieldValue('label', (option as ModelListItem).name)
} else {
form.setFieldValue('capability', (option as ModelListItem).capability)
} }
} }
@@ -137,16 +139,19 @@ const ModelConfigModal = forwardRef<ModelConfigModalRef, ModelConfigModalProps>(
rules={[{ required: source !== 'multi_agent', message: t('common.pleaseSelect') }]} rules={[{ required: source !== 'multi_agent', message: t('common.pleaseSelect') }]}
hidden={source === 'multi_agent'} hidden={source === 'multi_agent'}
> >
{source !== 'multi_agent' && <Select {source !== 'multi_agent' &&
placeholder={t('common.pleaseSelect')} <Select
fieldNames={{ placeholder={t('common.pleaseSelect')}
label: 'name', fieldNames={{
value: 'id', label: 'name',
}} value: 'id',
options={modelList} }}
onChange={handleChange} options={modelList}
/>} onChange={handleChange}
/>
}
</FormItem> </FormItem>
{source === 'model' && <FormItem name="capability" hidden />}
{source === 'chat' && <FormItem name="label" hidden />} {source === 'chat' && <FormItem name="label" hidden />}
<div className="rb:text-[14px] rb:font-medium rb:text-[#5B6167] rb:mb-4">{t('application.parameterConfig')}</div> <div className="rb:text-[14px] rb:font-medium rb:text-[#5B6167] rb:mb-4">{t('application.parameterConfig')}</div>

View File

@@ -2,7 +2,7 @@
* @Author: ZhaoYing * @Author: ZhaoYing
* @Date: 2026-02-03 16:29:49 * @Date: 2026-02-03 16:29:49
* @Last Modified by: ZhaoYing * @Last Modified by: ZhaoYing
* @Last Modified time: 2026-03-19 21:10:53 * @Last Modified time: 2026-03-24 10:59:18
*/ */
import type { KnowledgeConfig } from './components/Knowledge/types' import type { KnowledgeConfig } from './components/Knowledge/types'
import type { Variable } from './components/VariableList/types' import type { Variable } from './components/VariableList/types'
@@ -11,6 +11,7 @@ import type { ChatItem } from '@/components/Chat/types'
import type { GraphRef, WorkflowConfig } from '@/views/Workflow/types'; import type { GraphRef, WorkflowConfig } from '@/views/Workflow/types';
import type { ApiKey } from '@/views/ApiKeyManagement/types' import type { ApiKey } from '@/views/ApiKeyManagement/types'
import type { SkillConfigForm } from './components/Skill/types' import type { SkillConfigForm } from './components/Skill/types'
import type { Capability } from '@/views/ModelManagement/types'
/** /**
* Model configuration parameters * Model configuration parameters
@@ -20,6 +21,7 @@ export interface ModelConfig {
label?: string; label?: string;
/** Default model configuration ID */ /** Default model configuration ID */
default_model_config_id?: string; default_model_config_id?: string;
capability?: Capability[];
/** Temperature for response randomness (0-2) */ /** Temperature for response randomness (0-2) */
temperature: number; temperature: number;
/** Maximum tokens in response */ /** Maximum tokens in response */
@@ -60,6 +62,7 @@ export interface Config extends MultiAgentConfig {
system_prompt: string; system_prompt: string;
/** Default model configuration ID */ /** Default model configuration ID */
default_model_config_id?: string; default_model_config_id?: string;
capability?: Capability[];
/** Model parameters */ /** Model parameters */
model_parameters: ModelConfig; model_parameters: ModelConfig;
/** Knowledge retrieval configuration */ /** Knowledge retrieval configuration */