From 2ff9000d2527094d0b36e6d939b90a5447cdbfb9 Mon Sep 17 00:00:00 2001 From: zhaoying Date: Fri, 27 Feb 2026 13:45:31 +0800 Subject: [PATCH] feat(web): form add rules --- web/src/components/SearchInput/index.tsx | 2 + web/src/i18n/en.ts | 5 +- web/src/i18n/zh.ts | 5 +- web/src/utils/validator.ts | 52 +++++++++++++++++++ .../components/ApiKeyModal.tsx | 10 +++- web/src/views/ApplicationConfig/Agent.tsx | 10 ++-- .../components/ApplicationModal.tsx | 8 ++- .../components/MemberModal.tsx | 6 ++- .../components/MemoryForm.tsx | 8 ++- .../components/CustomModelModal.tsx | 20 +++++-- .../components/GroupModelModal.tsx | 23 ++++++-- web/src/views/ModelManagement/index.tsx | 1 + .../components/OntologyClassExtractModal.tsx | 5 +- .../components/OntologyClassModal.tsx | 8 ++- .../Ontology/components/OntologyModal.tsx | 8 ++- web/src/views/Skills/pages/SkillConfig.tsx | 10 +++- web/src/views/Skills/types.ts | 2 + .../SpaceManagement/components/SpaceModal.tsx | 12 +++-- 18 files changed, 167 insertions(+), 28 deletions(-) create mode 100644 web/src/utils/validator.ts diff --git a/web/src/components/SearchInput/index.tsx b/web/src/components/SearchInput/index.tsx index 32a64310..476c2cbb 100644 --- a/web/src/components/SearchInput/index.tsx +++ b/web/src/components/SearchInput/index.tsx @@ -41,6 +41,8 @@ interface SearchInputProps { className?: string; /** Input size */ size?: InputProps['size'] + /** Maximum length of the input value */ + maxLength?: number; } /** Search input component with debounce and throttle support */ diff --git a/web/src/i18n/en.ts b/web/src/i18n/en.ts index 02add0ec..c5c81f13 100644 --- a/web/src/i18n/en.ts +++ b/web/src/i18n/en.ts @@ -453,6 +453,8 @@ export const en = { prevStep: 'Previous Step', exportSuccess: 'Export successful', recommend: 'Recommend', + logoTip: `Supported image formats: JPG, PNG \n Suggested size: square ratio \n Maximum size: ≤ 2MB`, + imageSquareRequired: 'Please upload a square image', }, model: { searchPlaceholder: 'search model…', @@ -542,7 +544,8 @@ export const en = { ollama: "Ollama", xinference: "Xinference", gpustack: "Gpustack", - bedrock: "Bedrock" + bedrock: "Bedrock", + nameInvalid: 'Model name can only contain letters, numbers, underscores and spaces, cannot be empty or pure whitespace', }, modelNew: { group: 'Model Group', diff --git a/web/src/i18n/zh.ts b/web/src/i18n/zh.ts index 06abf63a..f63e5981 100644 --- a/web/src/i18n/zh.ts +++ b/web/src/i18n/zh.ts @@ -1029,6 +1029,8 @@ export const zh = { prevStep: '上一步', exportSuccess: '导出成功', recommend: '推荐', + logoTip: `支持图片格式(JPG、PNG)\n 尺寸:正方形比例 \n 文件大小限制:≤ 2MB`, + imageSquareRequired: '请上传正方形比例图片', }, model: { searchPlaceholder: '搜索模型…', @@ -1176,7 +1178,8 @@ export const zh = { ollama: "Ollama", xinference: "Xinference", gpustack: "Gpustack", - bedrock: "Bedrock" + bedrock: "Bedrock", + nameInvalid: '模型名称只能包含字母、数字、下划线和空格, 不能为空或纯空格', }, timezones: { 'Asia/Shanghai': '中国标准时间 (UTC+8)', diff --git a/web/src/utils/validator.ts b/web/src/utils/validator.ts new file mode 100644 index 00000000..70f0cb02 --- /dev/null +++ b/web/src/utils/validator.ts @@ -0,0 +1,52 @@ +/* + * @Author: zhaoying yzhao96@best-inc.com + * @Date: 2026-03-02 13:46:53 + * @LastEditors: zhaoying yzhao96@best-inc.com + * @LastEditTime: 2026-03-02 14:38:33 + * @FilePath: /web/src/utils/validator.ts + * @Description: 这是默认设置,请设置`customMade`, 打开koroFileHeader查看配置 进行设置: https://github.com/OBKoro1/koro1FileHeader/wiki/%E9%85%8D%E7%BD%AE + */ +/** + * Form validation utilities + */ + +interface UploadFile { + originFileObj: Blob; + [key: string]: unknown; +} + +/** + * Validate if uploaded image is square (width === height) + * @param errorMessage - Error message to display when validation fails + * @returns Ant Design form validator + */ +export const validateSquareImage = (errorMessage: string = 'Image must be square') => { + return (_: unknown, value: UploadFile | UploadFile[] | undefined) => { + if (!value || (Array.isArray(value) && value.length === 0)) { + return Promise.resolve(); + } + + const file = Array.isArray(value) ? value[0] : value; + + if (file?.originFileObj) { + return new Promise((resolve, reject) => { + const img = new Image(); + img.onload = () => { + if (img.width === img.height) { + resolve(); + } else { + reject(new Error(errorMessage)); + } + }; + img.onerror = () => reject(new Error('Failed to load image')); + img.src = URL.createObjectURL(file.originFileObj); + }); + } + + return Promise.resolve(); + }; +}; + +// - Cannot be empty or pure whitespace +// - Cannot start with a space +export const stringRegExp = /^[a-zA-Z0-9\u4e00-\u9fa5][a-zA-Z0-9\u4e00-\u9fa5\s]*$/ \ No newline at end of file diff --git a/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx b/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx index 9395df43..05e73992 100644 --- a/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx +++ b/web/src/views/ApiKeyManagement/components/ApiKeyModal.tsx @@ -12,6 +12,7 @@ import dayjs from 'dayjs' import type { ApiKey, ApiKeyModalRef } from '../types'; import RbModal from '@/components/RbModal' import { createApiKey, updateApiKey } from '@/api/apiKey'; +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -78,7 +79,7 @@ const ApiKeyModal = forwardRef(({ form.validateFields() .then((values) => { const { memory, rag, expires_at, ...rest } = values - let scopes = [] + const scopes = [] if (memory) { scopes.push('memory') @@ -130,7 +131,11 @@ const ApiKeyModal = forwardRef(({ @@ -138,6 +143,7 @@ const ApiKeyModal = forwardRef(({ diff --git a/web/src/views/ApplicationConfig/Agent.tsx b/web/src/views/ApplicationConfig/Agent.tsx index 2ece4b6e..4bee291b 100644 --- a/web/src/views/ApplicationConfig/Agent.tsx +++ b/web/src/views/ApplicationConfig/Agent.tsx @@ -169,8 +169,8 @@ const Agent = forwardRef((_props, ref) => { getApplicationConfig(id as string).then(res => { const response = res as Config const { skills, variables } = response - let allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : [] - let allTools = Array.isArray(response.tools) ? response.tools : [] + const allSkills = Array.isArray(skills?.skill_ids) ? skills?.skill_ids.map(vo => ({ id: vo })) : [] + const allTools = Array.isArray(response.tools) ? response.tools : [] const memoryContent = response.memory?.memory_config_id const parsedMemoryContent = memoryContent === null || memoryContent === '' ? undefined @@ -431,7 +431,11 @@ const Agent = forwardRef((_props, ref) => { - + ( diff --git a/web/src/views/MemberManagement/components/MemberModal.tsx b/web/src/views/MemberManagement/components/MemberModal.tsx index 002c8632..e16c60ba 100644 --- a/web/src/views/MemberManagement/components/MemberModal.tsx +++ b/web/src/views/MemberManagement/components/MemberModal.tsx @@ -152,7 +152,11 @@ const MemberModal = forwardRef(({ diff --git a/web/src/views/MemoryManagement/components/MemoryForm.tsx b/web/src/views/MemoryManagement/components/MemoryForm.tsx index 93246ca9..282199af 100644 --- a/web/src/views/MemoryManagement/components/MemoryForm.tsx +++ b/web/src/views/MemoryManagement/components/MemoryForm.tsx @@ -18,6 +18,7 @@ import RbModal from '@/components/RbModal' import { createMemoryConfig, updateMemoryConfig } from '@/api/memory' import { getOntologyScenesSimpleUrl } from '@/api/ontology' import CustomSelect from '@/components/CustomSelect'; +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -110,7 +111,11 @@ const MemoryForm = forwardRef(({ @@ -118,6 +123,7 @@ const MemoryForm = forwardRef(({ diff --git a/web/src/views/ModelManagement/components/CustomModelModal.tsx b/web/src/views/ModelManagement/components/CustomModelModal.tsx index 17373a02..112534a5 100644 --- a/web/src/views/ModelManagement/components/CustomModelModal.tsx +++ b/web/src/views/ModelManagement/components/CustomModelModal.tsx @@ -20,6 +20,7 @@ import CustomSelect from '@/components/CustomSelect' import UploadImages from '@/components/Upload/UploadImages' import { updateCustomModel, addCustomModel, modelTypeUrl, modelProviderUrl } from '@/api/models' import { getFileLink } from '@/api/fileStorage' +import { validateSquareImage, stringRegExp } from '@/utils/validator' /** * Custom model modal component @@ -65,7 +66,7 @@ const CustomModelModal = forwardRef( const res = isEdit ? updateCustomModel(model.id, rest) : addCustomModel(data) res.then(() => { - refresh && refresh(isEdit) + refresh?.(isEdit) handleClose() message.success(isEdit ? t('common.updateSuccess') : t('common.createSuccess')) }) @@ -79,7 +80,7 @@ const CustomModelModal = forwardRef( .validateFields() .then((values) => { const { logo, ...rest } = values; - let formData: CustomModelForm = { + const formData: CustomModelForm = { ...rest } @@ -125,14 +126,22 @@ const CustomModelModal = forwardRef( name="logo" label={t('modelNew.logo')} valuePropName="fileList" - rules={[{ required: true, message: t('common.pleaseSelect') }]} + rules={[ + { required: true, message: t('common.pleaseSelect') }, + { validator: validateSquareImage(t('common.imageSquareRequired')) } + ]} + extra={t('common.logoTip')?.split('\n').map((vo, index) =>
{vo}
)} > - +
@@ -166,6 +175,7 @@ const CustomModelModal = forwardRef( diff --git a/web/src/views/ModelManagement/components/GroupModelModal.tsx b/web/src/views/ModelManagement/components/GroupModelModal.tsx index efcd77f6..5ca46548 100644 --- a/web/src/views/ModelManagement/components/GroupModelModal.tsx +++ b/web/src/views/ModelManagement/components/GroupModelModal.tsx @@ -1,8 +1,8 @@ /* * @Author: ZhaoYing * @Date: 2026-02-03 16:49:33 - * @Last Modified by: ZhaoYing - * @Last Modified time: 2026-02-03 16:49:33 + * @Last Modified by: ZhaoYing + * @Last Modified time: 2026-03-02 12:23:13 */ /** * Group Model Modal @@ -21,6 +21,7 @@ import { updateCompositeModel, modelTypeUrl, addCompositeModel } from '@/api/mod import UploadImages from '@/components/Upload/UploadImages' import ModelImplement from './ModelImplement' import { getFileLink } from '@/api/fileStorage' +import { validateSquareImage, stringRegExp } from '@/utils/validator' /** * Group model modal component @@ -133,15 +134,26 @@ const GroupModelModal = forwardRef(({ name="logo" label={t('modelNew.logo')} valuePropName="fileList" - rules={[{ required: true, message: t('common.pleaseSelect') }]} + rules={[ + { required: true, message: t('common.pleaseSelect') }, + { validator: validateSquareImage(t('common.imageSquareRequired')) } + ]} + extra={t('common.logoTip')?.split('\n').map((vo, index) =>
{vo}
)} > - +
@@ -165,6 +177,7 @@ const GroupModelModal = forwardRef(({ diff --git a/web/src/views/ModelManagement/index.tsx b/web/src/views/ModelManagement/index.tsx index 35d7d864..539ff5e3 100644 --- a/web/src/views/ModelManagement/index.tsx +++ b/web/src/views/ModelManagement/index.tsx @@ -121,6 +121,7 @@ const tabKeys = ['group', 'list', 'square'] {activeTab !== 'list' && diff --git a/web/src/views/Ontology/components/OntologyClassExtractModal.tsx b/web/src/views/Ontology/components/OntologyClassExtractModal.tsx index 802202ef..2fd305c6 100644 --- a/web/src/views/Ontology/components/OntologyClassExtractModal.tsx +++ b/web/src/views/Ontology/components/OntologyClassExtractModal.tsx @@ -182,7 +182,10 @@ const OntologyClassExtractModal = forwardRef diff --git a/web/src/views/Ontology/components/OntologyClassModal.tsx b/web/src/views/Ontology/components/OntologyClassModal.tsx index 087e542c..a467294e 100644 --- a/web/src/views/Ontology/components/OntologyClassModal.tsx +++ b/web/src/views/Ontology/components/OntologyClassModal.tsx @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import type { AddClassItem, OntologyClassModalRef } from '../types' import RbModal from '@/components/RbModal' import { createOntologyClass } from '@/api/ontology' +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -105,7 +106,11 @@ const OntologyClassModal = forwardRef @@ -113,6 +118,7 @@ const OntologyClassModal = forwardRef diff --git a/web/src/views/Ontology/components/OntologyModal.tsx b/web/src/views/Ontology/components/OntologyModal.tsx index a4c203ed..92e94bb6 100644 --- a/web/src/views/Ontology/components/OntologyModal.tsx +++ b/web/src/views/Ontology/components/OntologyModal.tsx @@ -11,6 +11,7 @@ import { useTranslation } from 'react-i18next'; import type { OntologyItem, OntologyModalData, OntologyModalRef } from '../types' import RbModal from '@/components/RbModal' import { createOntologyScene, updateOntologyScene } from '@/api/ontology' +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -109,7 +110,11 @@ const OntologyModal = forwardRef(({ @@ -117,6 +122,7 @@ const OntologyModal = forwardRef(({ diff --git a/web/src/views/Skills/pages/SkillConfig.tsx b/web/src/views/Skills/pages/SkillConfig.tsx index 6e12e72d..f9f76dea 100644 --- a/web/src/views/Skills/pages/SkillConfig.tsx +++ b/web/src/views/Skills/pages/SkillConfig.tsx @@ -17,6 +17,7 @@ import type { AiPromptModalRef } from '@/views/ApplicationConfig/types' import exitIcon from '@/assets/images/knowledgeBase/exit.png'; import type { SkillFormData } from '../types' import { getSkillDetail, createSkill, updateSkill } from '@/api/skill' +import { stringRegExp } from '@/utils/validator'; /** * Skill Configuration Page Component @@ -110,7 +111,7 @@ const SkillConfig: FC = () => { // Format tools data for API const formData = { ...rest, - tools: tools?.map((item: any) => ({ + tools: tools?.map((item) => ({ tool_id: item.tool_id, operation: item.operation })) @@ -144,13 +145,18 @@ const SkillConfig: FC = () => { diff --git a/web/src/views/Skills/types.ts b/web/src/views/Skills/types.ts index 950bfb03..c0df3fbf 100644 --- a/web/src/views/Skills/types.ts +++ b/web/src/views/Skills/types.ts @@ -17,6 +17,8 @@ export interface SkillFormData { tools: Array<{ /** Tool identifier */ tool_id: string; + /** Tool operation/action */ + operation?: string; }>; /** Skill configuration settings */ config: { diff --git a/web/src/views/SpaceManagement/components/SpaceModal.tsx b/web/src/views/SpaceManagement/components/SpaceModal.tsx index 4f37b246..5a639244 100644 --- a/web/src/views/SpaceManagement/components/SpaceModal.tsx +++ b/web/src/views/SpaceManagement/components/SpaceModal.tsx @@ -23,6 +23,7 @@ import UploadImages from '@/components/Upload/UploadImages' import { getFileLink } from '@/api/fileStorage' import ragIcon from '@/assets/images/space/rag.png' import neo4jIcon from '@/assets/images/space/neo4j.png' +import { stringRegExp } from '@/utils/validator'; const FormItem = Form.Item; @@ -91,7 +92,7 @@ const SpaceModal = forwardRef(({ setCurrentStep(1) } else { const { icon, ...rest } = values - let formData: SpaceModalData = { + const formData: SpaceModalData = { ...rest } if (icon?.response?.data.file_id) { @@ -164,14 +165,19 @@ const SpaceModal = forwardRef(({ valuePropName="fileList" hidden={currentStep === 1} rules={[{ required: true, message: t('common.selectPlaceholder', { title: t('space.spaceIcon') }) }]} + extra={t('common.logoTip')?.split('\n').map((vo, index) =>
{vo}
)} > - +