/** * json-render 注册表 * * 创建组件注册表,将 Schema 定义与 React 组件绑定 * 用于 Renderer 的自动解析和渲染 */ 'use client'; import { useState } from 'react'; // ============ React 组件实现 ============ // Card 组件 const Card = ({ title, children, className, renderChildren, emit }: any) => (
{title &&

{title}

} {renderChildren ? renderChildren(children, emit) : null}
); // Stack 组件 const Stack = ({ direction = 'column', spacing = 2, align = 'start', children, className, renderChildren, emit }: any) => { const directionClass = direction === 'row' ? 'flex-row' : 'flex-col'; const spacingClass = spacing > 0 ? `gap-${spacing}` : ''; const alignClass = align === 'center' ? 'items-center' : align === 'end' ? 'items-end' : align === 'stretch' ? 'items-stretch' : 'items-start'; return (
{renderChildren ? renderChildren(children, emit) : null}
); }; // Heading 组件 const Heading = ({ level = 'h2', text, className }: any) => { const Tag = level; const sizeClasses: Record = { h1: 'text-3xl font-bold', h2: 'text-2xl font-semibold', h3: 'text-xl font-semibold', h4: 'text-lg font-medium', h5: 'text-base font-medium', h6: 'text-sm font-medium', }; const sizeClass = sizeClasses[level] || sizeClasses.h2; return {text}; }; // Text 组件 const Text = ({ content, variant = 'body', className }: any) => { const variantClasses: Record = { body: 'text-gray-700 dark:text-gray-300', muted: 'text-gray-500 dark:text-gray-400', code: 'font-mono text-sm bg-gray-100 dark:bg-gray-900 px-1 py-0.5 rounded', }; const variantClass = variantClasses[variant] || variantClasses.body; return

{content}

; }; // Button 组件 - 支持交互事件 const Button = ({ label, variant = 'default', onClick, action, actionPayload, disabled = false, className, emit }: any) => { const variantClasses: Record = { default: 'bg-gray-200 hover:bg-gray-300 text-gray-800 dark:bg-gray-700 dark:hover:bg-gray-600 dark:text-gray-200', primary: 'bg-blue-600 hover:bg-blue-700 text-white', secondary: 'bg-purple-600 hover:bg-purple-700 text-white', ghost: 'hover:bg-gray-100 dark:hover:bg-gray-800 text-gray-700 dark:text-gray-300', danger: 'bg-red-600 hover:bg-red-700 text-white', }; const variantClass = variantClasses[variant] || variantClasses.default; const handleClick = () => { if (disabled) return; // 优先使用 action 事件(emit) if (action && emit) { emit(action, actionPayload); return; } // 回退到 onClick 代码执行 if (onClick) { try { // 安全执行 onClick 代码 const fn = new Function('event', onClick); fn(new Event('click')); } catch (e) { console.error('Error executing onClick:', e); } } }; return ( ); }; // Input 组件 const Input = ({ placeholder, value, onChange, disabled = false, className, type = 'text' }: any) => { const handleChange = (e: React.ChangeEvent) => { if (onChange) { try { const fn = new Function('event', onChange); fn(e); } catch (err) { console.error('Error executing onChange:', err); } } }; return ( ); }; // Badge 组件 const Badge = ({ text, variant = 'default', className }: any) => { const variantClasses: Record = { default: 'bg-gray-100 dark:bg-gray-800 text-gray-800 dark:text-gray-200', success: 'bg-green-100 dark:bg-green-900/30 text-green-800 dark:text-green-200', warning: 'bg-yellow-100 dark:bg-yellow-900/30 text-yellow-800 dark:text-yellow-200', error: 'bg-red-100 dark:bg-red-900/30 text-red-800 dark:text-red-200', info: 'bg-blue-100 dark:bg-blue-900/30 text-blue-800 dark:text-blue-200', }; const variantClass = variantClasses[variant] || variantClasses.default; return ( {text} ); }; // Separator 组件 const Separator = ({ orientation = 'horizontal', className }: any) => { const orientationClass = orientation === 'horizontal' ? 'h-px w-full' : 'w-px h-full'; return
; }; // ============ MCP 专用组件实现 ============ // TranslationResult 组件 - 翻译结果展示(支持 emit 事件) const TranslationResult = ({ translated, termsUsed, className, emit }: any) => { const [copied, setCopied] = useState(false); const handleCopy = () => { // 使用 emit 触发复制事件(如果可用),否则直接调用 clipboard API if (emit) { emit('copy', { text: translated }); } else { navigator.clipboard.writeText(translated); } setCopied(true); setTimeout(() => setCopied(false), 2000); }; return (
{/* 头部 */}
🌐

翻译结果

{/* 内容区域 */}
{/* 译文 */}
译文

{translated}

{/* 使用的术语 */} {termsUsed && termsUsed.length > 0 && (
使用术语
{termsUsed.map((term: string, idx: number) => ( {term} ))}
)}
{/* 底部操作栏 */}
); }; // NovelList 组件 - 支持点击交互 const NovelList = ({ novels, className, emit }: any) => (
{novels.map((novel: any) => (
emit && emit('selectNovel', { id: novel.id, title: novel.title, ...novel })} className="bg-white dark:bg-gray-800 rounded-lg border dark:border-gray-700 p-4 hover:shadow-md hover:border-blue-300 dark:hover:border-blue-600 transition-all cursor-pointer" >

{novel.title}

{novel.author &&

By {novel.author}

} {novel.description &&

{novel.description}

}
{novel.chapterCount && } {novel.tags?.map((tag: string) => ( ))}
))}
); // ChapterReader 组件 const ChapterReader = ({ novelTitle, chapterTitle, content, chapterNumber, totalChapters, className }: any) => (

{novelTitle}

{chapterTitle}

{chapterNumber && totalChapters && (

Chapter {chapterNumber} of {totalChapters}

)}

{content}

); // McpToolCall 组件 const McpToolCall = ({ tool, status, args, result, error, className }: any) => { const statusConfig: Record = { pending: { icon: '⏳', color: 'text-yellow-600', bg: 'bg-yellow-50 dark:bg-yellow-900/20 border-yellow-300 dark:border-yellow-700' }, running: { icon: '🔄', color: 'text-blue-600', bg: 'bg-blue-50 dark:bg-blue-900/20 border-blue-300 dark:border-blue-700' }, success: { icon: '✅', color: 'text-green-600', bg: 'bg-green-50 dark:bg-green-900/20 border-green-300 dark:border-green-700' }, error: { icon: '❌', color: 'text-red-600', bg: 'bg-red-50 dark:bg-red-900/20 border-red-300 dark:border-red-700' }, }; const config = statusConfig[status] || statusConfig.success; return (
{config.icon} {tool}
{args && (

Arguments:

            {JSON.stringify(args, null, 2)}
          
)} {result && status === 'success' && (

Result:

            {JSON.stringify(result, null, 2)}
          
)} {error && status === 'error' && (

Error:

{error}

)}
); }; // LoginPanel 组件 const LoginPanel = ({ server, email, className }: any) => (
🔐

{server}

Sign in to continue

); // CodeBlock 组件 const CodeBlock = ({ code, language = 'text', inline = false, className }: any) => { if (inline) { return ( {code} ); } return (
{language}
        {code}
      
); }; // DataTable 组件 const DataTable = ({ columns, rows, className }: any) => (
{columns.map((col: any) => ( ))} {rows.map((row: any, idx: number) => ( {columns.map((col: any) => ( ))} ))}
{col.label}
{String(row[col.key] ?? '')}
); // NovelDetail 组件 - 小说详情卡片 const NovelDetail = ({ novel, className, emit }: any) => { if (!novel) return null; return (
{/* 头部 - 渐变背景 */}

{novel.title || '未知标题'}

{novel.isVip && ( 👑 VIP )}
{/* 内容区域 */}
{/* 基本信息 */}

📖 基本信息

作者:{novel.author || '-'}

类型:{novel.category || '-'}

简介:{novel.description || '暂无简介'}

{/* 统计数据 */}

📊 统计数据

状态

{novel.status || '-'}

章节数

{novel.chapterCount ?? '-'}章

总字数

{novel.wordCount ?? '-'}字

阅读量

{novel.viewCount ?? '-'}次

); }; // SuggestionButtons 组件 - 建议操作按钮组 const SuggestionButtons = ({ suggestions, className, emit }: any) => { if (!suggestions || !Array.isArray(suggestions) || suggestions.length === 0) return null; return (
{suggestions.map((suggestion: any, index: number) => { const handleClick = () => { if (emit && suggestion.message) { emit('sendMessage', { message: suggestion.message }); } }; return ( ); })}
); }; // ============ 创建注册表 ============ // 简化的组件注册表 - 直接映射组件名称到 React 组件 export const jsonRenderRegistry: Record> = { // 基础组件 card: Card, stack: Stack, heading: Heading, text: Text, button: Button, input: Input, badge: Badge, separator: Separator, // MCP 专用组件 'translation-result': TranslationResult, 'novel-list': NovelList, 'novel-detail': NovelDetail, 'chapter-reader': ChapterReader, 'mcp-tool-call': McpToolCall, 'login-panel': LoginPanel, 'code-block': CodeBlock, 'data-table': DataTable, 'suggestion-buttons': SuggestionButtons, }; // ============ 辅助函数 ============ /** * 递归渲染子组件 * 用于 Card 和 Stack 组件中渲染 children 数组 * @param children - 子组件数组 * @param emit - ActionContext emit 函数(用于组件交互) */ export function renderChildren(children: any, emit?: (eventName: string, payload?: any) => void): React.ReactNode { if (!children) return null; if (!Array.isArray(children)) return null; return children.map((child: any, idx: number) => { if (!child || typeof child !== 'object') return null; if (!child.type) return {String(child)}; const componentType = child.type; const Component = (jsonRenderRegistry as any)[componentType]; if (Component) { // 合并 props:原始 child props + emit 函数 const props: any = { ...child }; if (emit) { props.emit = emit; } // 如果组件需要渲染子组件,传入 renderChildren 函数(并传递 emit) if (componentType === 'card' || componentType === 'stack') { props.renderChildren = (grandchildren: any) => renderChildren(grandchildren, emit); } return ; } return (

Unknown component: {componentType}

); }); } // 导出类型 export type { ComponentSpec } from './json-render-catalog';