Skip to content

feat: 添加form表单的mcp工具 #22

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions docs/src/router/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,11 @@ const router = createRouter({
name: 'Comprehensive',
component: () => import('../views/comprehensive/index.vue')
},
{
path: '/form',
name: 'Form',
component: () => import('../views/form/form-validate.vue')
},
{
path: '/:pathMatch(.*)*',
name: 'NotFound',
Expand Down
151 changes: 151 additions & 0 deletions docs/src/views/form/form-validate.vue
Original file line number Diff line number Diff line change
@@ -0,0 +1,151 @@
<template>
<div class="demo-form">
<div class="title">
<div>validate 用法:<tiny-button-group :data="validTypeList" v-model="validType"></tiny-button-group></div>
</div>
<tiny-form
ref="ruleFormRef"
:model="createData"
:tiny_mcp_config="{
server,
business: {
id: 'form-validate',
description: '表单验证示例'
}
}"
:rules="rules"
label-width="100px"
>
<tiny-form-item label="等级" prop="radio">
<tiny-radio-group v-model="createData.radio" :options="options"></tiny-radio-group>
</tiny-form-item>
<tiny-form-item label="IP字段" prop="ip">
<tiny-ip-address v-model="createData.ip"></tiny-ip-address>
</tiny-form-item>
<tiny-form-item label="必填" prop="users" :validate-icon="validateIcon">
<tiny-input v-model="createData.users"></tiny-input>
</tiny-form-item>
<tiny-form-item label="日期" prop="datepicker">
<tiny-date-picker v-model="createData.datepicker"></tiny-date-picker>
</tiny-form-item>
<tiny-form-item label="URL" prop="url">
<tiny-input v-model="createData.url"></tiny-input>
</tiny-form-item>
<tiny-form-item label="邮件" prop="email">
<tiny-input v-model="createData.email"></tiny-input>
</tiny-form-item>
<tiny-form-item label="数字" prop="num1">
<tiny-numeric v-model="createData.num1"></tiny-numeric>
</tiny-form-item>
<tiny-form-item>
<tiny-button type="primary" @click="validType === 'callback' ? handleSubmit() : handleSubmitPromise()">
提交
</tiny-button>
<tiny-button @click="clearFormValid"> 移除校验 </tiny-button>
<tiny-button @click="resetForm"> 重置表单 </tiny-button>
</tiny-form-item>
</tiny-form>
</div>
</template>

<script setup>
import { ref, reactive } from 'vue'
import {
TinyForm,
TinyFormItem,
TinyInput,
TinyDatePicker,
TinyButton,
TinyModal,
TinyRadioGroup,
TinyNumeric,
TinyIpAddress,
TinyButtonGroup
} from '@opentiny/vue'
import { iconWarning } from '@opentiny/vue-icon'
import { useNextServer } from '@opentiny/next-vue'

const ruleFormRef = ref()
function handleClick() {
TinyModal.message({ message: 'click', status: 'info' })
}
const validateIcon = ref(iconWarning())
const validType = ref('promise')
const validTypeList = ref([
{ text: 'promise', value: 'promise' },
{ text: '回调', value: 'callback' }
])
const options = ref([
{ label: 'A', text: '很好', events: { click: handleClick } },
{ label: 'B', text: '一般' }
])
const createData = reactive({
radio: '',
users: '',
url: '',
email: '',
datepicker: '',
ip: '',
num1: 0
})
const rules = ref({
radio: [{ required: true, message: '必填', trigger: 'change' }],
users: [
{ required: true, message: '必填', trigger: 'blur' },
{ min: 2, max: 11, message: '长度必须不小于 2', trigger: ['change', 'blur'] }
],
datepicker: { type: 'date' },
url: { type: 'url' },
email: { type: 'email' },
ip: [
{
validator: (rule, value, cb) => (value === '1.1.1.1' ? cb() : cb(new Error('必填 1.1.1.1'))),
trigger: 'change'
}
],
num1: [{ type: 'number', min: 2, max: 11, message: '必填 2~11 之间的数字', trigger: 'change' }]
})

function handleSubmit() {
ruleFormRef.value.validate((valid) => {
if (valid) {
TinyModal.alert('回调用法:提交成功')
} else {
TinyModal.alert('回调用法:提交失败')
}
})
}

function handleSubmitPromise() {
ruleFormRef.value
.validate()
.then(() => {
TinyModal.alert('promise 用法:提交成功')
})
.catch(() => {
TinyModal.alert('promise 用法:提交失败')
})
}

function clearFormValid() {
ruleFormRef.value.clearValidate()
}

function resetForm() {
ruleFormRef.value.resetFields()
}

const { server } = useNextServer({
serverInfo: { name: 'ecs-form', version: '1.0.0' }
})
</script>

<style scoped>
.demo-form {
width: 450px;
}
.title {
margin-bottom: 20px;
font-size: 14px;
}
</style>
4 changes: 3 additions & 1 deletion packages/mcp/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import { getGridConfig } from './src/grid'
import { getBaseSelectConfig } from './src/base-select'
import { i18n } from './src/utils/locale'
import { getButtonConfig } from './src/button'
import { getFormConfig } from './src/form'

export { zhCN, enUS, i18n }

Expand All @@ -29,7 +30,8 @@ export const getTinyVueMcpConfig = ({ t }: { t?: ((path: string) => string) | nu
components: {
Grid: getGridConfig(),
BaseSelect: getBaseSelectConfig(),
Button: getButtonConfig()
Button: getButtonConfig(),
Form: getFormConfig()
}
}
}
67 changes: 67 additions & 0 deletions packages/mcp/src/form/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,67 @@
import { z } from 'zod'
import { defineComponentTool } from '../utils/defineComponentTool'
import resourcesZh from './resouces.zh.md?raw'
import resourcesEn from './resouces.en.md?raw'
import { t } from '../utils/locale'

export const resources = {
zh: resourcesZh,
en: resourcesEn
}

export const getFormConfig = () =>
defineComponentTool({
name: 'form_component_tools',
description: t('ai.form.description'),
tools: {
resetFields: {
paramsSchema: z.boolean().optional().describe(t('ai.form.resetFields')),
cb: (instance) => {
instance.resetFields()
return { type: 'text', text: 'success' }
}
},
clearValidate: {
paramsSchema: z.string().optional().describe(t('ai.form.clearValidate')),
cb: (instance, value) => {
if (typeof value === 'string' && value) {
const arr = value.split(',')
if(arr.length > 0) {
instance.clearValidate(arr)
}else {
instance.clearValidate(value)
}
}
return { type: 'text', text: 'success' }
}
},
Comment on lines +24 to +37
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix logic error in field name handling.

The condition if(arr.length > 0) will always be true after split(',') because split always returns at least one element. This makes the else block unreachable and the logic incorrect.

Apply this diff to fix the logic:

-        cb: (instance, value) => {
-          if (typeof value === 'string' && value) {
-            const arr = value.split(',')
-            if(arr.length > 0) {
-             instance.clearValidate(arr)
-            }else {
-              instance.clearValidate(value)
-            }
-          } 
-          return { type: 'text', text: 'success' }
-        }
+        cb: (instance, value) => {
+          if (typeof value === 'string' && value) {
+            const arr = value.split(',').map(s => s.trim()).filter(s => s)
+            if (arr.length > 1) {
+              instance.clearValidate(arr)
+            } else if (arr.length === 1) {
+              instance.clearValidate(arr[0])
+            }
+          } else {
+            instance.clearValidate()
+          }
+          return { type: 'text', text: 'success' }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
clearValidate: {
paramsSchema: z.string().optional().describe(t('ai.form.clearValidate')),
cb: (instance, value) => {
if (typeof value === 'string' && value) {
const arr = value.split(',')
if(arr.length > 0) {
instance.clearValidate(arr)
}else {
instance.clearValidate(value)
}
}
return { type: 'text', text: 'success' }
}
},
clearValidate: {
paramsSchema: z.string().optional().describe(t('ai.form.clearValidate')),
cb: (instance, value) => {
if (typeof value === 'string' && value) {
const arr = value
.split(',')
.map(s => s.trim())
.filter(s => s)
if (arr.length > 1) {
instance.clearValidate(arr)
} else if (arr.length === 1) {
instance.clearValidate(arr[0])
}
} else {
instance.clearValidate()
}
return { type: 'text', text: 'success' }
}
},
🤖 Prompt for AI Agents
In packages/mcp/src/form/index.ts around lines 24 to 37, the condition checking
if arr.length > 0 after splitting the string is always true, making the else
block unreachable and the logic incorrect. Fix this by removing the else block
and always calling instance.clearValidate with the array resulting from
splitting the string, ensuring correct handling of the field names.

clearValidateAll: {
paramsSchema: z.boolean().optional().describe(t('ai.form.clearValidateAll')),
cb: (instance) => {
instance.clearValidate()
return { type: 'text', text: 'success' }
}
},
validate: {
paramsSchema: z.boolean().optional().describe(t('ai.form.validate')),
cb: async (instance) => {
const result = await instance.validate()
return { type: 'text', text: result ? 'success' : 'fail' }
}
},
validateField: {
paramsSchema: z.string().optional().describe(t('ai.form.validateField')),
cb: (instance, value) => {
if (typeof value === 'string' && value) {
const arr = value.split(',')
if(arr.length > 0) {
instance.validateField(arr)
}else {
instance.validateField(value)
}
}
return { type: 'text', text: 'success' }
}
}
Comment on lines +52 to +65
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue

Fix identical logic error in validateField.

This tool has the same logic error as clearValidate - the condition if(arr.length > 0) will always be true after split, making the else block unreachable.

Apply this diff to fix the logic:

-        cb: (instance, value) => {
-          if (typeof value === 'string' && value) {
-            const arr = value.split(',')
-            if(arr.length > 0) {
-             instance.validateField(arr)
-            }else {
-              instance.validateField(value)
-            }
-          } 
-          return { type: 'text', text: 'success' }
-        }
+        cb: (instance, value) => {
+          if (typeof value === 'string' && value) {
+            const arr = value.split(',').map(s => s.trim()).filter(s => s)
+            if (arr.length > 1) {
+              instance.validateField(arr)
+            } else if (arr.length === 1) {
+              instance.validateField(arr[0])
+            }
+          }
+          return { type: 'text', text: 'success' }
+        }
📝 Committable suggestion

‼️ IMPORTANT
Carefully review the code before committing. Ensure that it accurately replaces the highlighted code, contains no missing lines, and has no issues with indentation. Thoroughly test & benchmark the code to ensure it meets the requirements.

Suggested change
validateField: {
paramsSchema: z.string().optional().describe(t('ai.form.validateField')),
cb: (instance, value) => {
if (typeof value === 'string' && value) {
const arr = value.split(',')
if(arr.length > 0) {
instance.validateField(arr)
}else {
instance.validateField(value)
}
}
return { type: 'text', text: 'success' }
}
}
validateField: {
paramsSchema: z.string().optional().describe(t('ai.form.validateField')),
cb: (instance, value) => {
if (typeof value === 'string' && value) {
const arr = value
.split(',')
.map(s => s.trim())
.filter(s => s)
if (arr.length > 1) {
instance.validateField(arr)
} else if (arr.length === 1) {
instance.validateField(arr[0])
}
}
return { type: 'text', text: 'success' }
}
}
🤖 Prompt for AI Agents
In packages/mcp/src/form/index.ts around lines 52 to 65, the validateField
callback contains a logic error where the else block is unreachable because
after splitting a non-empty string by ',', the resulting array length is always
greater than 0. To fix this, remove the unnecessary else block and always call
instance.validateField with the array when value is a non-empty string. This
eliminates the redundant condition and unreachable code.

}
})
3 changes: 3 additions & 0 deletions packages/mcp/src/form/resouces.en.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Form Component

Composed of buttons, input boxes, selectors, radio buttons, multiple-choice boxes, and other controls, used for collecting, verifying, and submitting data.
3 changes: 3 additions & 0 deletions packages/mcp/src/form/resouces.zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Form 表单

由按钮、输入框、选择器、单选框、多选框等控件组成,用以收集、校验、提交数据。
9 changes: 9 additions & 0 deletions packages/mcp/src/lang/en.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,15 @@ export default {
button: {
description: 'Button component related tool set',
triggerClick: 'Button click, click the button'
},
form: {
description:
'Form component related tool set, including getting form validation, resetting form and other functions',
resetFields: 'Reset the entire form, resetting all field values to their initial values and removing validation results',
clearValidate: 'Remove the validation results of the form item, you can pass in the prop of the form item to be removed, or an array of props, if not passed in, the validation results of the entire form will be removed',
clearValidateAll: 'Remove the validation results of the entire form',
validate: 'Validate the entire form, the parameter is a callback function (the callback function will be called after the validation is completed and passed two parameters: 1, whether the validation is successful 2, the fields that failed validation) returns a Promise object',
validateField: 'Validate the specified field by passing in the prop of the form item to be validated, or separating the props with commas'
}
}
}
8 changes: 8 additions & 0 deletions packages/mcp/src/lang/zh-CN.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,14 @@ export default {
button: {
description: '按钮组件相关工具集合',
triggerClick: '按钮点击,点击按钮'
},
form: {
description: '表单组件相关工具集合,包含获取表单校验、重置表单等功能',
resetFields: '对整个表单进行重置,将所有字段值重置为初始值并移除校验结果',
clearValidate: '移除表单项的校验结果,可传入待移除的表单项的 prop ,或者 prop 用逗号隔开,如不传则移除整个表单的校验结果',
clearValidateAll: '移除整个表单的校验结果',
validate: '对整个表单进行校验的方法,参数为一个回调函数(该回调函数会在校验结束后被调用,并传入两个参数:1、是否校验成功 2、未通过校验的字段)返回一个 Promise对象',
validateField: '对指定字段进行校验,可传入待校验的表单项的 prop ,或者 prop 用逗号隔开'
}
}
}