Skip to main content
Glama

MCP Probe Kit

by mybolide
genui.ts18.9 kB
// genui 工具实现 export async function genui(args: any) { try { const description = args?.description || ""; const framework = args?.framework || "react"; // react, vue, html const message = `请生成以下 UI 组件: 📝 **组件描述**: ${description || "请描述需要的 UI 组件"} ⚛️ **框架**:${framework} --- ## UI 组件生成指南 ### 第一步:理解需求 **组件类型**: - 基础组件(Button, Input, Card) - 表单组件(Form, Select, Checkbox) - 数据展示(Table, List, Grid) - 反馈组件(Modal, Toast, Loading) - 导航组件(Menu, Tabs, Breadcrumb) - 布局组件(Layout, Container, Flex) ### 第二步:设计原则 **1️⃣ 组件化** - 单一职责 - 可复用 - 可组合 **2️⃣ 可访问性(A11y)** - 语义化 HTML - ARIA 属性 - 键盘导航 - 屏幕阅读器支持 **3️⃣ 响应式** - 移动端优先 - 断点设计 - 弹性布局 **4️⃣ 性能** - 懒加载 - 虚拟滚动 - 防抖节流 --- ## React 组件示例 ### 基础 Button 组件 \`\`\`tsx import React from 'react'; import { cva, type VariantProps } from 'class-variance-authority'; const buttonVariants = cva( // 基础样式 "inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50", { variants: { variant: { default: "bg-blue-600 text-white hover:bg-blue-700", destructive: "bg-red-600 text-white hover:bg-red-700", outline: "border border-gray-300 bg-transparent hover:bg-gray-100", ghost: "hover:bg-gray-100", link: "text-blue-600 underline-offset-4 hover:underline", }, size: { default: "h-10 px-4 py-2", sm: "h-9 px-3", lg: "h-11 px-8", icon: "h-10 w-10", }, }, defaultVariants: { variant: "default", size: "default", }, } ); export interface ButtonProps extends React.ButtonHTMLAttributes<HTMLButtonElement>, VariantProps<typeof buttonVariants> { isLoading?: boolean; } const Button = React.forwardRef<HTMLButtonElement, ButtonProps>( ({ className, variant, size, isLoading, children, ...props }, ref) => { return ( <button className={buttonVariants({ variant, size, className })} ref={ref} disabled={isLoading || props.disabled} {...props} > {isLoading && ( <svg className="mr-2 h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle className="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" strokeWidth="4" /> <path className="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> )} {children} </button> ); } ); Button.displayName = "Button"; export { Button, buttonVariants }; \`\`\` **使用示例:** \`\`\`tsx <Button variant="default" size="lg"> 提交 </Button> <Button variant="outline" isLoading> 加载中... </Button> <Button variant="ghost" size="icon"> <Icon /> </Button> \`\`\` --- ### 表单输入组件 \`\`\`tsx import React from 'react'; import { useController, Control } from 'react-hook-form'; interface InputProps extends React.InputHTMLAttributes<HTMLInputElement> { label?: string; error?: string; helperText?: string; control?: Control<any>; name: string; } export const Input = React.forwardRef<HTMLInputElement, InputProps>( ({ label, error, helperText, className, control, name, ...props }, ref) => { const { field } = useController({ name, control, defaultValue: props.defaultValue || '', }); return ( <div className="w-full space-y-2"> {label && ( <label htmlFor={name} className="text-sm font-medium text-gray-700" > {label} {props.required && <span className="text-red-500 ml-1">*</span>} </label> )} <input {...field} {...props} ref={ref} id={name} aria-invalid={!!error} aria-describedby={error ? \`\${name}-error\` : undefined} className={\` w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed \${error ? 'border-red-500' : 'border-gray-300'} \${className || ''} \`} /> {error && ( <p id={\`\${name}-error\`} className="text-sm text-red-600"> {error} </p> )} {helperText && !error && ( <p className="text-sm text-gray-500">{helperText}</p> )} </div> ); } ); Input.displayName = 'Input'; \`\`\` --- ### Modal 弹窗组件 \`\`\`tsx import React, { useEffect } from 'react'; import { createPortal } from 'react-dom'; interface ModalProps { isOpen: boolean; onClose: () => void; title?: string; children: React.ReactNode; size?: 'sm' | 'md' | 'lg' | 'xl'; } export const Modal: React.FC<ModalProps> = ({ isOpen, onClose, title, children, size = 'md', }) => { // ESC 关闭 useEffect(() => { const handleEsc = (e: KeyboardEvent) => { if (e.key === 'Escape') onClose(); }; if (isOpen) { document.addEventListener('keydown', handleEsc); document.body.style.overflow = 'hidden'; } return () => { document.removeEventListener('keydown', handleEsc); document.body.style.overflow = 'unset'; }; }, [isOpen, onClose]); if (!isOpen) return null; const sizeClasses = { sm: 'max-w-md', md: 'max-w-lg', lg: 'max-w-2xl', xl: 'max-w-4xl', }; return createPortal( <div className="fixed inset-0 z-50 flex items-center justify-center" onClick={onClose} > {/* 背景遮罩 */} <div className="absolute inset-0 bg-black/50 backdrop-blur-sm" /> {/* 弹窗内容 */} <div className={\` relative bg-white rounded-lg shadow-xl w-full mx-4 \${sizeClasses[size]} max-h-[90vh] flex flex-col animate-in fade-in slide-in-from-bottom-4 \`} onClick={(e) => e.stopPropagation()} role="dialog" aria-modal="true" aria-labelledby={title ? 'modal-title' : undefined} > {/* 头部 */} {title && ( <div className="px-6 py-4 border-b"> <h2 id="modal-title" className="text-xl font-semibold"> {title} </h2> </div> )} {/* 关闭按钮 */} <button onClick={onClose} className="absolute top-4 right-4 text-gray-400 hover:text-gray-600" aria-label="关闭" > <svg className="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path strokeLinecap="round" strokeLinejoin="round" strokeWidth={2} d="M6 18L18 6M6 6l12 12" /> </svg> </button> {/* 内容 */} <div className="px-6 py-4 overflow-y-auto flex-1"> {children} </div> </div> </div>, document.body ); }; \`\`\` --- ### Table 数据表格 \`\`\`tsx import React from 'react'; interface Column<T> { key: keyof T | string; title: string; render?: (value: any, record: T, index: number) => React.ReactNode; width?: string; align?: 'left' | 'center' | 'right'; } interface TableProps<T> { columns: Column<T>[]; data: T[]; rowKey: keyof T; loading?: boolean; onRowClick?: (record: T) => void; } export function Table<T extends Record<string, any>>({ columns, data, rowKey, loading, onRowClick, }: TableProps<T>) { if (loading) { return <div className="text-center py-8">加载中...</div>; } if (data.length === 0) { return <div className="text-center py-8 text-gray-500">暂无数据</div>; } return ( <div className="overflow-x-auto"> <table className="min-w-full divide-y divide-gray-200"> <thead className="bg-gray-50"> <tr> {columns.map((col, index) => ( <th key={String(col.key) || index} style={{ width: col.width }} className={\` px-6 py-3 text-xs font-medium text-gray-500 uppercase tracking-wider text-\${col.align || 'left'} \`} > {col.title} </th> ))} </tr> </thead> <tbody className="bg-white divide-y divide-gray-200"> {data.map((record, rowIndex) => ( <tr key={String(record[rowKey])} onClick={() => onRowClick?.(record)} className={onRowClick ? 'cursor-pointer hover:bg-gray-50' : ''} > {columns.map((col, colIndex) => { const value = col.key in record ? record[col.key as keyof T] : undefined; return ( <td key={colIndex} className={\`px-6 py-4 whitespace-nowrap text-sm text-\${col.align || 'left'}\`} > {col.render ? col.render(value, record, rowIndex) : String(value)} </td> ); })} </tr> ))} </tbody> </table> </div> ); } \`\`\` --- ## Tailwind CSS 配置 \`\`\`js // tailwind.config.js module.exports = { content: ['./src/**/*.{js,ts,jsx,tsx}'], theme: { extend: { keyframes: { 'fade-in': { from: { opacity: 0 }, to: { opacity: 1 }, }, 'slide-in-from-bottom': { from: { transform: 'translateY(1rem)' }, to: { transform: 'translateY(0)' }, }, }, animation: { 'fade-in': 'fade-in 0.2s ease-out', 'slide-in-from-bottom-4': 'slide-in-from-bottom 0.3s ease-out', }, }, }, plugins: [], }; \`\`\` --- ## Vue 3 组件示例 ### 基础 Button 组件 (Vue 3 + TypeScript) \`\`\`vue <template> <button :class="buttonClasses" :disabled="isLoading || disabled" v-bind="$attrs" > <svg v-if="isLoading" class="mr-2 h-4 w-4 animate-spin" xmlns="http://www.w3.org/2000/svg" fill="none" viewBox="0 0 24 24" > <circle class="opacity-25" cx="12" cy="12" r="10" stroke="currentColor" stroke-width="4" /> <path class="opacity-75" fill="currentColor" d="M4 12a8 8 0 018-8V0C5.373 0 0 5.373 0 12h4zm2 5.291A7.962 7.962 0 014 12H0c0 3.042 1.135 5.824 3 7.938l3-2.647z" /> </svg> <slot /> </button> </template> <script setup lang="ts"> import { computed } from 'vue'; interface Props { variant?: 'default' | 'destructive' | 'outline' | 'ghost' | 'link'; size?: 'default' | 'sm' | 'lg' | 'icon'; isLoading?: boolean; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { variant: 'default', size: 'default', isLoading: false, disabled: false, }); const buttonClasses = computed(() => { const base = 'inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus-visible:outline-none focus-visible:ring-2 focus-visible:ring-offset-2 disabled:pointer-events-none disabled:opacity-50'; const variants = { default: 'bg-blue-600 text-white hover:bg-blue-700', destructive: 'bg-red-600 text-white hover:bg-red-700', outline: 'border border-gray-300 bg-transparent hover:bg-gray-100', ghost: 'hover:bg-gray-100', link: 'text-blue-600 underline-offset-4 hover:underline', }; const sizes = { default: 'h-10 px-4 py-2', sm: 'h-9 px-3', lg: 'h-11 px-8', icon: 'h-10 w-10', }; return \`\${base} \${variants[props.variant]} \${sizes[props.size]}\`; }); </script> \`\`\` **使用示例:** \`\`\`vue <template> <div> <Button variant="default" size="lg">提交</Button> <Button variant="outline" :is-loading="true">加载中...</Button> <Button variant="ghost" size="icon"> <Icon /> </Button> </div> </template> <script setup lang="ts"> import Button from '@/components/Button.vue'; </script> \`\`\` --- ### Vue 3 Input 组件 \`\`\`vue <template> <div class="w-full space-y-2"> <label v-if="label" :for="inputId" class="text-sm font-medium text-gray-700" > {{ label }} <span v-if="required" class="text-red-500 ml-1">*</span> </label> <input :id="inputId" v-model="model" :type="type" :placeholder="placeholder" :required="required" :disabled="disabled" :aria-invalid="!!error" :aria-describedby="error ? \`\${inputId}-error\` : undefined" :class="inputClasses" v-bind="$attrs" /> <p v-if="error" :id="\`\${inputId}-error\`" class="text-sm text-red-600"> {{ error }} </p> <p v-else-if="helperText" class="text-sm text-gray-500"> {{ helperText }} </p> </div> </template> <script setup lang="ts"> import { computed, useAttrs } from 'vue'; interface Props { modelValue?: string | number; label?: string; type?: string; placeholder?: string; error?: string; helperText?: string; required?: boolean; disabled?: boolean; } const props = withDefaults(defineProps<Props>(), { type: 'text', required: false, disabled: false, }); const emit = defineEmits<{ 'update:modelValue': [value: string | number]; }>(); const attrs = useAttrs(); const inputId = computed(() => attrs.id as string || \`input-\${Math.random().toString(36).slice(2)}\`); const model = computed({ get: () => props.modelValue, set: (value) => emit('update:modelValue', value), }); const inputClasses = computed(() => { return \` w-full px-3 py-2 border rounded-md focus:outline-none focus:ring-2 focus:ring-blue-500 disabled:bg-gray-100 disabled:cursor-not-allowed \${props.error ? 'border-red-500' : 'border-gray-300'} \`; }); </script> \`\`\` --- ### Vue 3 Modal 组件 \`\`\`vue <template> <Teleport to="body"> <Transition name="modal"> <div v-if="modelValue" class="fixed inset-0 z-50 flex items-center justify-center" @click="handleClose" > <!-- 背景遮罩 --> <div class="absolute inset-0 bg-black/50 backdrop-blur-sm" /> <!-- 弹窗内容 --> <div :class="modalClasses" @click.stop role="dialog" aria-modal="true" :aria-labelledby="title ? 'modal-title' : undefined" > <!-- 头部 --> <div v-if="title" class="px-6 py-4 border-b"> <h2 id="modal-title" class="text-xl font-semibold"> {{ title }} </h2> </div> <!-- 关闭按钮 --> <button @click="handleClose" class="absolute top-4 right-4 text-gray-400 hover:text-gray-600" aria-label="关闭" > <svg class="w-6 h-6" fill="none" viewBox="0 0 24 24" stroke="currentColor"> <path stroke-linecap="round" stroke-linejoin="round" stroke-width="2" d="M6 18L18 6M6 6l12 12" /> </svg> </button> <!-- 内容 --> <div class="px-6 py-4 overflow-y-auto flex-1"> <slot /> </div> </div> </div> </Transition> </Teleport> </template> <script setup lang="ts"> import { computed, watch } from 'vue'; import { onKeyStroke } from '@vueuse/core'; interface Props { modelValue: boolean; title?: string; size?: 'sm' | 'md' | 'lg' | 'xl'; } const props = withDefaults(defineProps<Props>(), { size: 'md', }); const emit = defineEmits<{ 'update:modelValue': [value: boolean]; }>(); const handleClose = () => { emit('update:modelValue', false); }; // ESC 关闭 onKeyStroke('Escape', () => { if (props.modelValue) { handleClose(); } }); // 锁定 body 滚动 watch(() => props.modelValue, (isOpen) => { document.body.style.overflow = isOpen ? 'hidden' : ''; }); const modalClasses = computed(() => { const sizeClasses = { sm: 'max-w-md', md: 'max-w-lg', lg: 'max-w-2xl', xl: 'max-w-4xl', }; return \` relative bg-white rounded-lg shadow-xl w-full mx-4 \${sizeClasses[props.size]} max-h-[90vh] flex flex-col \`; }); </script> <style scoped> .modal-enter-active, .modal-leave-active { transition: opacity 0.3s ease; } .modal-enter-from, .modal-leave-to { opacity: 0; } </style> \`\`\` --- ## 组件库推荐 ### 🎨 React UI 组件库 - **shadcn/ui** - 可定制的 React 组件 - **Radix UI** - 无样式的可访问组件 - **Headless UI** - Tailwind 官方无样式组件 - **Ant Design** - 企业级 UI 设计语言 - **Material-UI** - Google Material Design ### 🎨 Vue UI 组件库 - **Element Plus** - 基于 Vue 3 的企业级组件库 - **Naive UI** - 完整的 TypeScript 支持 - **Ant Design Vue** - Vue 版本的 Ant Design - **Vuetify** - Material Design 组件框架 - **PrimeVue** - 丰富的 UI 组件集合 ### 🎭 动画库 - **Framer Motion** - React 动画库 - **React Spring** - 基于物理的动画 - **GSAP** - 高性能动画引擎(React/Vue 通用) - **@vueuse/motion** - Vue 3 组合式动画 ### 📱 响应式工具 - **Tailwind CSS** - 实用优先的 CSS 框架 - **UnoCSS** - 即时原子化 CSS 引擎 - **CSS Modules** - 局部作用域 CSS --- 现在请根据需求生成完整的 UI 组件代码,包括: 1. 组件实现(TypeScript) 2. 样式(Tailwind CSS) 3. 使用示例 4. Props 接口定义 5. 可访问性说明 **根据框架选择:** - **React**: 使用 Hooks、forwardRef、TypeScript - **Vue 3**: 使用 Composition API、script setup、TypeScript - **HTML**: 使用原生 JavaScript 和 Web Components`; return { content: [ { type: "text", text: message, }, ], }; } catch (error) { const errorMessage = error instanceof Error ? error.message : String(error); return { content: [ { type: "text", text: `❌ 生成 UI 组件失败: ${errorMessage}`, }, ], isError: true, }; } }

MCP directory API

We provide all the information about MCP servers via our MCP API.

curl -X GET 'https://glama.ai/api/mcp/v1/servers/mybolide/mcp-probe-kit'

If you have feedback or need assistance with the MCP directory API, please join our Discord server