Skip to content

Commit 0b223ef

Browse files
myshell-joeshanexiCherwaywaytiancheng-myshellikun97
authored andcommitted
Dev input validation (#377)
Co-authored-by: shanexi <[email protected]> Co-authored-by: Chengwei Ouyang <[email protected]> Co-authored-by: tiancheng-myshell <[email protected]> Co-authored-by: Chengwei Ouyang <[email protected]> Co-authored-by: ikun97 <[email protected]> Co-authored-by: Wenliang Zhao <[email protected]> Co-authored-by: yuxumin <[email protected]>
1 parent 7f34fc2 commit 0b223ef

File tree

7 files changed

+148
-568
lines changed

7 files changed

+148
-568
lines changed

web/apps/web/src/components/app/node-form/index.tsx

+2
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,7 @@ import {
4848
StateSelector,
4949
TimerSelector,
5050
ContextSelector,
51+
InputValidation,
5152
} from './widgets';
5253

5354
interface NodeFormProps {
@@ -124,6 +125,7 @@ const NodeForm = forwardRef<FormRef, NodeFormProps>(
124125
StateSelector,
125126
TimerSelector,
126127
ContextSelector,
128+
InputValidation,
127129
...components,
128130
}}
129131
/>

web/apps/web/src/components/app/node-form/widgets/index.tsx

+1
Original file line numberDiff line numberDiff line change
@@ -12,3 +12,4 @@ export * from './validate-render/dialog';
1212
export * from './state-selector';
1313
export * from './timer-selector';
1414
export * from './context-selector';
15+
export * from './input-validations';
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
import React, { useEffect, useMemo, useRef } from 'react';
2+
import {
3+
useFormContext,
4+
FormField,
5+
Select,
6+
NumberInput,
7+
Input,
8+
} from '@shellagent/ui';
9+
import { useFormEngineContext } from '@shellagent/form-engine';
10+
11+
export interface InputValidationProps {
12+
name?: string;
13+
}
14+
15+
// 文件类型列表
16+
const FILE_TYPES = ['image', 'audio', 'video', 'text_file', 'file'];
17+
18+
// 获取类型类别
19+
const getTypeCategory = (type: string): string => {
20+
if (type === 'text') return 'text';
21+
if (FILE_TYPES.includes(type)) return 'file';
22+
return 'other';
23+
};
24+
25+
export const InputValidation: React.FC<InputValidationProps> = props => {
26+
const { name = '' } = props;
27+
const { fields, remove } = useFormEngineContext();
28+
const { control, watch, setValue, getValues } = useFormContext();
29+
const { schema } = fields[name] || {};
30+
const prevCategoryRef = useRef<string | null>(null);
31+
32+
const type = watch('type');
33+
const ruleType = getValues(`${name}.rule_type`);
34+
35+
const currentCategory = getTypeCategory(type);
36+
37+
const options = useMemo(() => {
38+
if (currentCategory === 'text') {
39+
return [{ label: 'Max Length', value: 'max_length' }];
40+
} else if (currentCategory === 'file') {
41+
return [{ label: 'Max File Size', value: 'max_file_size' }];
42+
}
43+
return [];
44+
}, [currentCategory]);
45+
46+
useEffect(() => {
47+
if (
48+
prevCategoryRef.current &&
49+
prevCategoryRef.current !== currentCategory
50+
) {
51+
remove(name);
52+
return;
53+
}
54+
55+
prevCategoryRef.current = currentCategory;
56+
57+
if (options.length === 1 && !ruleType) {
58+
const defaultValue = options[0].value;
59+
setValue(`${name}.rule_type`, defaultValue);
60+
}
61+
62+
if (ruleType === 'max_length' && !getValues(`${name}.max_length`)) {
63+
setValue(`${name}.max_length`, 500);
64+
} else if (
65+
ruleType === 'max_file_size' &&
66+
!getValues(`${name}.max_file_size`)
67+
) {
68+
setValue(`${name}.max_file_size`, 10 * 1024 * 1024);
69+
}
70+
}, [currentCategory, options, ruleType, name, setValue, getValues, remove]);
71+
72+
if (!schema) {
73+
return null;
74+
}
75+
76+
const renderRuleInput = () => {
77+
switch (ruleType) {
78+
case 'max_length':
79+
return (
80+
<FormField
81+
control={control}
82+
name={`${name}.max_length`}
83+
render={({ field }) => (
84+
<NumberInput
85+
{...field}
86+
size="sm"
87+
placeholder="Maximum Length"
88+
min={0}
89+
max={15000}
90+
/>
91+
)}
92+
/>
93+
);
94+
case 'max_file_size':
95+
return (
96+
<FormField
97+
control={control}
98+
name={`${name}.max_file_size`}
99+
render={({ field }) => (
100+
<NumberInput
101+
{...field}
102+
size="sm"
103+
placeholder="Maximum File Size (bytes)"
104+
min={0}
105+
max={100 * 1024 * 1024}
106+
/>
107+
)}
108+
/>
109+
);
110+
default:
111+
return null;
112+
}
113+
};
114+
115+
return (
116+
<div className="flex flex-col gap-2">
117+
<div className="grid grid-cols-3 gap-2">
118+
<FormField
119+
control={control}
120+
name={`${name}.rule_type`}
121+
render={({ field }) => (
122+
<Select
123+
{...field}
124+
size="sm"
125+
placeholder="Validation Rule Type"
126+
options={options}
127+
/>
128+
)}
129+
/>
130+
{renderRuleInput()}
131+
<FormField
132+
control={control}
133+
name={`${name}.error_message`}
134+
render={({ field }) => (
135+
<Input {...field} size="sm" placeholder="Error Message" />
136+
)}
137+
/>
138+
</div>
139+
</div>
140+
);
141+
};

web/apps/web/src/stores/app/schema/condition-state-schema.ts

+1-142
Original file line numberDiff line numberDiff line change
@@ -524,148 +524,7 @@ export const getConditionStateSchema: ISchema = {
524524
'x-title-size': 'h5',
525525
additionalItems: {
526526
type: 'object',
527-
properties: {
528-
void_core: {
529-
type: 'void',
530-
'x-role': 'core',
531-
'x-type': 'Grid',
532-
'x-class': 'grid-cols-3',
533-
properties: {
534-
rule_type: {
535-
type: 'string',
536-
enum: [
537-
'max_length',
538-
'min_number',
539-
'max_number',
540-
'max_file_size',
541-
],
542-
'x-component': 'Select',
543-
'x-component-props': {
544-
size: 'sm',
545-
placeholder: 'Rule type',
546-
options: [
547-
{ label: 'Max Length', value: 'max_length' },
548-
{ label: 'Min Number', value: 'min_number' },
549-
{ label: 'Max Number', value: 'max_number' },
550-
{ label: 'Max File Size', value: 'max_file_size' },
551-
],
552-
},
553-
'x-value-prop-name': 'defaultValue',
554-
'x-onchange-prop-name': 'onValueChange',
555-
'x-type': 'Control',
556-
'x-class': 'border-0 bg-inherit rounded-lg p-0',
557-
'x-reactions': [
558-
{
559-
target: 'max_length',
560-
when: '$this.value === "max_length"',
561-
fullfill: {
562-
schema: {
563-
'x-hidden': false,
564-
},
565-
},
566-
},
567-
{
568-
target: 'min_number',
569-
when: '$this.value === "min_number"',
570-
fullfill: {
571-
schema: {
572-
'x-hidden': false,
573-
},
574-
},
575-
},
576-
{
577-
target: 'max_number',
578-
when: '$this.value === "max_number"',
579-
fullfill: {
580-
schema: {
581-
'x-hidden': false,
582-
},
583-
},
584-
},
585-
{
586-
target: 'max_file_size',
587-
when: '$this.value === "max_file_size"',
588-
fullfill: {
589-
schema: {
590-
'x-hidden': false,
591-
},
592-
},
593-
},
594-
],
595-
},
596-
max_length: {
597-
type: 'number',
598-
default: 500,
599-
'x-component': 'NumberInput',
600-
'x-type': 'Control',
601-
// 'x-hidden': true,
602-
'x-component-props': {
603-
min: 0,
604-
max: 15000,
605-
placeholder: 'Rule limit',
606-
size: 'sm',
607-
},
608-
'x-class': 'border-0 bg-inherit rounded-lg p-0',
609-
'x-validator': [
610-
{ required: true },
611-
{ exclusiveMinimum: 0 },
612-
{ exclusiveMaximum: 15000 },
613-
],
614-
},
615-
min_number: {
616-
type: 'number',
617-
'x-component': 'NumberInput',
618-
'x-type': 'Control',
619-
'x-hidden': true,
620-
'x-component-props': {
621-
placeholder: 'Rule limit',
622-
size: 'sm',
623-
},
624-
'x-class': 'border-0 bg-inherit rounded-lg p-0',
625-
},
626-
max_number: {
627-
type: 'number',
628-
'x-component': 'NumberInput',
629-
'x-type': 'Control',
630-
'x-hidden': true,
631-
'x-component-props': {
632-
placeholder: 'Rule limit',
633-
size: 'sm',
634-
},
635-
'x-class': 'border-0 bg-inherit rounded-lg p-0',
636-
},
637-
max_file_size: {
638-
type: 'number',
639-
default: 10 * 1024 * 1024,
640-
'x-component': 'NumberInput',
641-
'x-type': 'Control',
642-
'x-hidden': true,
643-
'x-component-props': {
644-
min: 0,
645-
max: 100 * 1024 * 1024,
646-
placeholder: 'Rule limit',
647-
size: 'sm',
648-
},
649-
'x-class': 'border-0 bg-inherit rounded-lg p-0',
650-
'x-validator': [
651-
{ required: true },
652-
{ exclusiveMinimum: 0 },
653-
{ exclusiveMaximum: 100 * 1024 * 1024 },
654-
],
655-
},
656-
error_message: {
657-
type: 'string',
658-
'x-component': 'Input',
659-
'x-type': 'Control',
660-
'x-component-props': {
661-
size: 'sm',
662-
placeholder: 'Error Message',
663-
},
664-
'x-class': 'border-0 bg-inherit rounded-lg p-0',
665-
},
666-
},
667-
},
668-
},
527+
'x-component': 'InputValidation',
669528
'x-type': 'Inline',
670529
'x-role': 'core',
671530
'x-deletable': true,

0 commit comments

Comments
 (0)